##// END OF EJS Templates
merge with stable
Augie Fackler -
r42453:838f3a09 merge default
parent child Browse files
Show More
@@ -1,3384 +1,3384 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy as copymod
10 import copy as copymod
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 bookmarks,
24 bookmarks,
25 changelog,
25 changelog,
26 copies,
26 copies,
27 crecord as crecordmod,
27 crecord as crecordmod,
28 dirstateguard,
28 dirstateguard,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 merge as mergemod,
34 merge as mergemod,
35 mergeutil,
35 mergeutil,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 pycompat,
40 pycompat,
41 revlog,
41 revlog,
42 rewriteutil,
42 rewriteutil,
43 scmutil,
43 scmutil,
44 smartset,
44 smartset,
45 subrepoutil,
45 subrepoutil,
46 templatekw,
46 templatekw,
47 templater,
47 templater,
48 util,
48 util,
49 vfs as vfsmod,
49 vfs as vfsmod,
50 )
50 )
51
51
52 from .utils import (
52 from .utils import (
53 dateutil,
53 dateutil,
54 stringutil,
54 stringutil,
55 )
55 )
56
56
57 stringio = util.stringio
57 stringio = util.stringio
58
58
59 # templates of common command options
59 # templates of common command options
60
60
61 dryrunopts = [
61 dryrunopts = [
62 ('n', 'dry-run', None,
62 ('n', 'dry-run', None,
63 _('do not perform actions, just print output')),
63 _('do not perform actions, just print output')),
64 ]
64 ]
65
65
66 confirmopts = [
66 confirmopts = [
67 ('', 'confirm', None,
67 ('', 'confirm', None,
68 _('ask before applying actions')),
68 _('ask before applying actions')),
69 ]
69 ]
70
70
71 remoteopts = [
71 remoteopts = [
72 ('e', 'ssh', '',
72 ('e', 'ssh', '',
73 _('specify ssh command to use'), _('CMD')),
73 _('specify ssh command to use'), _('CMD')),
74 ('', 'remotecmd', '',
74 ('', 'remotecmd', '',
75 _('specify hg command to run on the remote side'), _('CMD')),
75 _('specify hg command to run on the remote side'), _('CMD')),
76 ('', 'insecure', None,
76 ('', 'insecure', None,
77 _('do not verify server certificate (ignoring web.cacerts config)')),
77 _('do not verify server certificate (ignoring web.cacerts config)')),
78 ]
78 ]
79
79
80 walkopts = [
80 walkopts = [
81 ('I', 'include', [],
81 ('I', 'include', [],
82 _('include names matching the given patterns'), _('PATTERN')),
82 _('include names matching the given patterns'), _('PATTERN')),
83 ('X', 'exclude', [],
83 ('X', 'exclude', [],
84 _('exclude names matching the given patterns'), _('PATTERN')),
84 _('exclude names matching the given patterns'), _('PATTERN')),
85 ]
85 ]
86
86
87 commitopts = [
87 commitopts = [
88 ('m', 'message', '',
88 ('m', 'message', '',
89 _('use text as commit message'), _('TEXT')),
89 _('use text as commit message'), _('TEXT')),
90 ('l', 'logfile', '',
90 ('l', 'logfile', '',
91 _('read commit message from file'), _('FILE')),
91 _('read commit message from file'), _('FILE')),
92 ]
92 ]
93
93
94 commitopts2 = [
94 commitopts2 = [
95 ('d', 'date', '',
95 ('d', 'date', '',
96 _('record the specified date as commit date'), _('DATE')),
96 _('record the specified date as commit date'), _('DATE')),
97 ('u', 'user', '',
97 ('u', 'user', '',
98 _('record the specified user as committer'), _('USER')),
98 _('record the specified user as committer'), _('USER')),
99 ]
99 ]
100
100
101 formatteropts = [
101 formatteropts = [
102 ('T', 'template', '',
102 ('T', 'template', '',
103 _('display with template'), _('TEMPLATE')),
103 _('display with template'), _('TEMPLATE')),
104 ]
104 ]
105
105
106 templateopts = [
106 templateopts = [
107 ('', 'style', '',
107 ('', 'style', '',
108 _('display using template map file (DEPRECATED)'), _('STYLE')),
108 _('display using template map file (DEPRECATED)'), _('STYLE')),
109 ('T', 'template', '',
109 ('T', 'template', '',
110 _('display with template'), _('TEMPLATE')),
110 _('display with template'), _('TEMPLATE')),
111 ]
111 ]
112
112
113 logopts = [
113 logopts = [
114 ('p', 'patch', None, _('show patch')),
114 ('p', 'patch', None, _('show patch')),
115 ('g', 'git', None, _('use git extended diff format')),
115 ('g', 'git', None, _('use git extended diff format')),
116 ('l', 'limit', '',
116 ('l', 'limit', '',
117 _('limit number of changes displayed'), _('NUM')),
117 _('limit number of changes displayed'), _('NUM')),
118 ('M', 'no-merges', None, _('do not show merges')),
118 ('M', 'no-merges', None, _('do not show merges')),
119 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 ('', 'stat', None, _('output diffstat-style summary of changes')),
120 ('G', 'graph', None, _("show the revision DAG")),
120 ('G', 'graph', None, _("show the revision DAG")),
121 ] + templateopts
121 ] + templateopts
122
122
123 diffopts = [
123 diffopts = [
124 ('a', 'text', None, _('treat all files as text')),
124 ('a', 'text', None, _('treat all files as text')),
125 ('g', 'git', None, _('use git extended diff format')),
125 ('g', 'git', None, _('use git extended diff format')),
126 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
126 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
127 ('', 'nodates', None, _('omit dates from diff headers'))
127 ('', 'nodates', None, _('omit dates from diff headers'))
128 ]
128 ]
129
129
130 diffwsopts = [
130 diffwsopts = [
131 ('w', 'ignore-all-space', None,
131 ('w', 'ignore-all-space', None,
132 _('ignore white space when comparing lines')),
132 _('ignore white space when comparing lines')),
133 ('b', 'ignore-space-change', None,
133 ('b', 'ignore-space-change', None,
134 _('ignore changes in the amount of white space')),
134 _('ignore changes in the amount of white space')),
135 ('B', 'ignore-blank-lines', None,
135 ('B', 'ignore-blank-lines', None,
136 _('ignore changes whose lines are all blank')),
136 _('ignore changes whose lines are all blank')),
137 ('Z', 'ignore-space-at-eol', None,
137 ('Z', 'ignore-space-at-eol', None,
138 _('ignore changes in whitespace at EOL')),
138 _('ignore changes in whitespace at EOL')),
139 ]
139 ]
140
140
141 diffopts2 = [
141 diffopts2 = [
142 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
142 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
143 ('p', 'show-function', None, _('show which function each change is in')),
143 ('p', 'show-function', None, _('show which function each change is in')),
144 ('', 'reverse', None, _('produce a diff that undoes the changes')),
144 ('', 'reverse', None, _('produce a diff that undoes the changes')),
145 ] + diffwsopts + [
145 ] + diffwsopts + [
146 ('U', 'unified', '',
146 ('U', 'unified', '',
147 _('number of lines of context to show'), _('NUM')),
147 _('number of lines of context to show'), _('NUM')),
148 ('', 'stat', None, _('output diffstat-style summary of changes')),
148 ('', 'stat', None, _('output diffstat-style summary of changes')),
149 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
149 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
150 ]
150 ]
151
151
152 mergetoolopts = [
152 mergetoolopts = [
153 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
153 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
154 ]
154 ]
155
155
156 similarityopts = [
156 similarityopts = [
157 ('s', 'similarity', '',
157 ('s', 'similarity', '',
158 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
159 ]
159 ]
160
160
161 subrepoopts = [
161 subrepoopts = [
162 ('S', 'subrepos', None,
162 ('S', 'subrepos', None,
163 _('recurse into subrepositories'))
163 _('recurse into subrepositories'))
164 ]
164 ]
165
165
166 debugrevlogopts = [
166 debugrevlogopts = [
167 ('c', 'changelog', False, _('open changelog')),
167 ('c', 'changelog', False, _('open changelog')),
168 ('m', 'manifest', False, _('open manifest')),
168 ('m', 'manifest', False, _('open manifest')),
169 ('', 'dir', '', _('open directory manifest')),
169 ('', 'dir', '', _('open directory manifest')),
170 ]
170 ]
171
171
172 # special string such that everything below this line will be ingored in the
172 # special string such that everything below this line will be ingored in the
173 # editor text
173 # editor text
174 _linebelow = "^HG: ------------------------ >8 ------------------------$"
174 _linebelow = "^HG: ------------------------ >8 ------------------------$"
175
175
176 def ishunk(x):
176 def ishunk(x):
177 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
177 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
178 return isinstance(x, hunkclasses)
178 return isinstance(x, hunkclasses)
179
179
180 def newandmodified(chunks, originalchunks):
180 def newandmodified(chunks, originalchunks):
181 newlyaddedandmodifiedfiles = set()
181 newlyaddedandmodifiedfiles = set()
182 for chunk in chunks:
182 for chunk in chunks:
183 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
183 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
184 originalchunks):
184 originalchunks):
185 newlyaddedandmodifiedfiles.add(chunk.header.filename())
185 newlyaddedandmodifiedfiles.add(chunk.header.filename())
186 return newlyaddedandmodifiedfiles
186 return newlyaddedandmodifiedfiles
187
187
188 def parsealiases(cmd):
188 def parsealiases(cmd):
189 return cmd.split("|")
189 return cmd.split("|")
190
190
191 def setupwrapcolorwrite(ui):
191 def setupwrapcolorwrite(ui):
192 # wrap ui.write so diff output can be labeled/colorized
192 # wrap ui.write so diff output can be labeled/colorized
193 def wrapwrite(orig, *args, **kw):
193 def wrapwrite(orig, *args, **kw):
194 label = kw.pop(r'label', '')
194 label = kw.pop(r'label', '')
195 for chunk, l in patch.difflabel(lambda: args):
195 for chunk, l in patch.difflabel(lambda: args):
196 orig(chunk, label=label + l)
196 orig(chunk, label=label + l)
197
197
198 oldwrite = ui.write
198 oldwrite = ui.write
199 def wrap(*args, **kwargs):
199 def wrap(*args, **kwargs):
200 return wrapwrite(oldwrite, *args, **kwargs)
200 return wrapwrite(oldwrite, *args, **kwargs)
201 setattr(ui, 'write', wrap)
201 setattr(ui, 'write', wrap)
202 return oldwrite
202 return oldwrite
203
203
204 def filterchunks(ui, originalhunks, usecurses, testfile, match,
204 def filterchunks(ui, originalhunks, usecurses, testfile, match,
205 operation=None):
205 operation=None):
206 try:
206 try:
207 if usecurses:
207 if usecurses:
208 if testfile:
208 if testfile:
209 recordfn = crecordmod.testdecorator(
209 recordfn = crecordmod.testdecorator(
210 testfile, crecordmod.testchunkselector)
210 testfile, crecordmod.testchunkselector)
211 else:
211 else:
212 recordfn = crecordmod.chunkselector
212 recordfn = crecordmod.chunkselector
213
213
214 return crecordmod.filterpatch(ui, originalhunks, recordfn,
214 return crecordmod.filterpatch(ui, originalhunks, recordfn,
215 operation)
215 operation)
216 except crecordmod.fallbackerror as e:
216 except crecordmod.fallbackerror as e:
217 ui.warn('%s\n' % e.message)
217 ui.warn('%s\n' % e.message)
218 ui.warn(_('falling back to text mode\n'))
218 ui.warn(_('falling back to text mode\n'))
219
219
220 return patch.filterpatch(ui, originalhunks, match, operation)
220 return patch.filterpatch(ui, originalhunks, match, operation)
221
221
222 def recordfilter(ui, originalhunks, match, operation=None):
222 def recordfilter(ui, originalhunks, match, operation=None):
223 """ Prompts the user to filter the originalhunks and return a list of
223 """ Prompts the user to filter the originalhunks and return a list of
224 selected hunks.
224 selected hunks.
225 *operation* is used for to build ui messages to indicate the user what
225 *operation* is used for to build ui messages to indicate the user what
226 kind of filtering they are doing: reverting, committing, shelving, etc.
226 kind of filtering they are doing: reverting, committing, shelving, etc.
227 (see patch.filterpatch).
227 (see patch.filterpatch).
228 """
228 """
229 usecurses = crecordmod.checkcurses(ui)
229 usecurses = crecordmod.checkcurses(ui)
230 testfile = ui.config('experimental', 'crecordtest')
230 testfile = ui.config('experimental', 'crecordtest')
231 oldwrite = setupwrapcolorwrite(ui)
231 oldwrite = setupwrapcolorwrite(ui)
232 try:
232 try:
233 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
233 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
234 testfile, match, operation)
234 testfile, match, operation)
235 finally:
235 finally:
236 ui.write = oldwrite
236 ui.write = oldwrite
237 return newchunks, newopts
237 return newchunks, newopts
238
238
239 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
239 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
240 filterfn, *pats, **opts):
240 filterfn, *pats, **opts):
241 opts = pycompat.byteskwargs(opts)
241 opts = pycompat.byteskwargs(opts)
242 if not ui.interactive():
242 if not ui.interactive():
243 if cmdsuggest:
243 if cmdsuggest:
244 msg = _('running non-interactively, use %s instead') % cmdsuggest
244 msg = _('running non-interactively, use %s instead') % cmdsuggest
245 else:
245 else:
246 msg = _('running non-interactively')
246 msg = _('running non-interactively')
247 raise error.Abort(msg)
247 raise error.Abort(msg)
248
248
249 # make sure username is set before going interactive
249 # make sure username is set before going interactive
250 if not opts.get('user'):
250 if not opts.get('user'):
251 ui.username() # raise exception, username not provided
251 ui.username() # raise exception, username not provided
252
252
253 def recordfunc(ui, repo, message, match, opts):
253 def recordfunc(ui, repo, message, match, opts):
254 """This is generic record driver.
254 """This is generic record driver.
255
255
256 Its job is to interactively filter local changes, and
256 Its job is to interactively filter local changes, and
257 accordingly prepare working directory into a state in which the
257 accordingly prepare working directory into a state in which the
258 job can be delegated to a non-interactive commit command such as
258 job can be delegated to a non-interactive commit command such as
259 'commit' or 'qrefresh'.
259 'commit' or 'qrefresh'.
260
260
261 After the actual job is done by non-interactive command, the
261 After the actual job is done by non-interactive command, the
262 working directory is restored to its original state.
262 working directory is restored to its original state.
263
263
264 In the end we'll record interesting changes, and everything else
264 In the end we'll record interesting changes, and everything else
265 will be left in place, so the user can continue working.
265 will be left in place, so the user can continue working.
266 """
266 """
267
267
268 checkunfinished(repo, commit=True)
268 checkunfinished(repo, commit=True)
269 wctx = repo[None]
269 wctx = repo[None]
270 merge = len(wctx.parents()) > 1
270 merge = len(wctx.parents()) > 1
271 if merge:
271 if merge:
272 raise error.Abort(_('cannot partially commit a merge '
272 raise error.Abort(_('cannot partially commit a merge '
273 '(use "hg commit" instead)'))
273 '(use "hg commit" instead)'))
274
274
275 def fail(f, msg):
276 raise error.Abort('%s: %s' % (f, msg))
277
278 force = opts.get('force')
279 if not force:
280 vdirs = []
281 match.explicitdir = vdirs.append
282 match.bad = fail
283
275 status = repo.status(match=match)
284 status = repo.status(match=match)
276
285
277 overrides = {(b'ui', b'commitsubrepos'): True}
286 overrides = {(b'ui', b'commitsubrepos'): True}
278
287
279 with repo.ui.configoverride(overrides, b'record'):
288 with repo.ui.configoverride(overrides, b'record'):
280 # subrepoutil.precommit() modifies the status
289 # subrepoutil.precommit() modifies the status
281 tmpstatus = scmutil.status(copymod.copy(status[0]),
290 tmpstatus = scmutil.status(copymod.copy(status[0]),
282 copymod.copy(status[1]),
291 copymod.copy(status[1]),
283 copymod.copy(status[2]),
292 copymod.copy(status[2]),
284 copymod.copy(status[3]),
293 copymod.copy(status[3]),
285 copymod.copy(status[4]),
294 copymod.copy(status[4]),
286 copymod.copy(status[5]),
295 copymod.copy(status[5]),
287 copymod.copy(status[6]))
296 copymod.copy(status[6]))
288
297
289 # Force allows -X subrepo to skip the subrepo.
298 # Force allows -X subrepo to skip the subrepo.
290 subs, commitsubs, newstate = subrepoutil.precommit(
299 subs, commitsubs, newstate = subrepoutil.precommit(
291 repo.ui, wctx, tmpstatus, match, force=True)
300 repo.ui, wctx, tmpstatus, match, force=True)
292 for s in subs:
301 for s in subs:
293 if s in commitsubs:
302 if s in commitsubs:
294 dirtyreason = wctx.sub(s).dirtyreason(True)
303 dirtyreason = wctx.sub(s).dirtyreason(True)
295 raise error.Abort(dirtyreason)
304 raise error.Abort(dirtyreason)
296
305
297 def fail(f, msg):
298 raise error.Abort('%s: %s' % (f, msg))
299
300 force = opts.get('force')
301 if not force:
302 vdirs = []
303 match.explicitdir = vdirs.append
304 match.bad = fail
305
306 if not force:
306 if not force:
307 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
307 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
308 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
308 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
309 section='commands',
309 section='commands',
310 configprefix='commit.interactive.')
310 configprefix='commit.interactive.')
311 diffopts.nodates = True
311 diffopts.nodates = True
312 diffopts.git = True
312 diffopts.git = True
313 diffopts.showfunc = True
313 diffopts.showfunc = True
314 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
314 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
315 originalchunks = patch.parsepatch(originaldiff)
315 originalchunks = patch.parsepatch(originaldiff)
316 match = scmutil.match(repo[None], pats)
316 match = scmutil.match(repo[None], pats)
317
317
318 # 1. filter patch, since we are intending to apply subset of it
318 # 1. filter patch, since we are intending to apply subset of it
319 try:
319 try:
320 chunks, newopts = filterfn(ui, originalchunks, match)
320 chunks, newopts = filterfn(ui, originalchunks, match)
321 except error.PatchError as err:
321 except error.PatchError as err:
322 raise error.Abort(_('error parsing patch: %s') % err)
322 raise error.Abort(_('error parsing patch: %s') % err)
323 opts.update(newopts)
323 opts.update(newopts)
324
324
325 # We need to keep a backup of files that have been newly added and
325 # We need to keep a backup of files that have been newly added and
326 # modified during the recording process because there is a previous
326 # modified during the recording process because there is a previous
327 # version without the edit in the workdir
327 # version without the edit in the workdir
328 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
328 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
329 contenders = set()
329 contenders = set()
330 for h in chunks:
330 for h in chunks:
331 try:
331 try:
332 contenders.update(set(h.files()))
332 contenders.update(set(h.files()))
333 except AttributeError:
333 except AttributeError:
334 pass
334 pass
335
335
336 changed = status.modified + status.added + status.removed
336 changed = status.modified + status.added + status.removed
337 newfiles = [f for f in changed if f in contenders]
337 newfiles = [f for f in changed if f in contenders]
338 if not newfiles:
338 if not newfiles:
339 ui.status(_('no changes to record\n'))
339 ui.status(_('no changes to record\n'))
340 return 0
340 return 0
341
341
342 modified = set(status.modified)
342 modified = set(status.modified)
343
343
344 # 2. backup changed files, so we can restore them in the end
344 # 2. backup changed files, so we can restore them in the end
345
345
346 if backupall:
346 if backupall:
347 tobackup = changed
347 tobackup = changed
348 else:
348 else:
349 tobackup = [f for f in newfiles if f in modified or f in
349 tobackup = [f for f in newfiles if f in modified or f in
350 newlyaddedandmodifiedfiles]
350 newlyaddedandmodifiedfiles]
351 backups = {}
351 backups = {}
352 if tobackup:
352 if tobackup:
353 backupdir = repo.vfs.join('record-backups')
353 backupdir = repo.vfs.join('record-backups')
354 try:
354 try:
355 os.mkdir(backupdir)
355 os.mkdir(backupdir)
356 except OSError as err:
356 except OSError as err:
357 if err.errno != errno.EEXIST:
357 if err.errno != errno.EEXIST:
358 raise
358 raise
359 try:
359 try:
360 # backup continues
360 # backup continues
361 for f in tobackup:
361 for f in tobackup:
362 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
362 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
363 dir=backupdir)
363 dir=backupdir)
364 os.close(fd)
364 os.close(fd)
365 ui.debug('backup %r as %r\n' % (f, tmpname))
365 ui.debug('backup %r as %r\n' % (f, tmpname))
366 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
366 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
367 backups[f] = tmpname
367 backups[f] = tmpname
368
368
369 fp = stringio()
369 fp = stringio()
370 for c in chunks:
370 for c in chunks:
371 fname = c.filename()
371 fname = c.filename()
372 if fname in backups:
372 if fname in backups:
373 c.write(fp)
373 c.write(fp)
374 dopatch = fp.tell()
374 dopatch = fp.tell()
375 fp.seek(0)
375 fp.seek(0)
376
376
377 # 2.5 optionally review / modify patch in text editor
377 # 2.5 optionally review / modify patch in text editor
378 if opts.get('review', False):
378 if opts.get('review', False):
379 patchtext = (crecordmod.diffhelptext
379 patchtext = (crecordmod.diffhelptext
380 + crecordmod.patchhelptext
380 + crecordmod.patchhelptext
381 + fp.read())
381 + fp.read())
382 reviewedpatch = ui.edit(patchtext, "",
382 reviewedpatch = ui.edit(patchtext, "",
383 action="diff",
383 action="diff",
384 repopath=repo.path)
384 repopath=repo.path)
385 fp.truncate(0)
385 fp.truncate(0)
386 fp.write(reviewedpatch)
386 fp.write(reviewedpatch)
387 fp.seek(0)
387 fp.seek(0)
388
388
389 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
389 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
390 # 3a. apply filtered patch to clean repo (clean)
390 # 3a. apply filtered patch to clean repo (clean)
391 if backups:
391 if backups:
392 # Equivalent to hg.revert
392 # Equivalent to hg.revert
393 m = scmutil.matchfiles(repo, backups.keys())
393 m = scmutil.matchfiles(repo, backups.keys())
394 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
394 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
395 force=True, matcher=m)
395 force=True, matcher=m)
396
396
397 # 3b. (apply)
397 # 3b. (apply)
398 if dopatch:
398 if dopatch:
399 try:
399 try:
400 ui.debug('applying patch\n')
400 ui.debug('applying patch\n')
401 ui.debug(fp.getvalue())
401 ui.debug(fp.getvalue())
402 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
402 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
403 except error.PatchError as err:
403 except error.PatchError as err:
404 raise error.Abort(pycompat.bytestr(err))
404 raise error.Abort(pycompat.bytestr(err))
405 del fp
405 del fp
406
406
407 # 4. We prepared working directory according to filtered
407 # 4. We prepared working directory according to filtered
408 # patch. Now is the time to delegate the job to
408 # patch. Now is the time to delegate the job to
409 # commit/qrefresh or the like!
409 # commit/qrefresh or the like!
410
410
411 # Make all of the pathnames absolute.
411 # Make all of the pathnames absolute.
412 newfiles = [repo.wjoin(nf) for nf in newfiles]
412 newfiles = [repo.wjoin(nf) for nf in newfiles]
413 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
413 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
414 finally:
414 finally:
415 # 5. finally restore backed-up files
415 # 5. finally restore backed-up files
416 try:
416 try:
417 dirstate = repo.dirstate
417 dirstate = repo.dirstate
418 for realname, tmpname in backups.iteritems():
418 for realname, tmpname in backups.iteritems():
419 ui.debug('restoring %r to %r\n' % (tmpname, realname))
419 ui.debug('restoring %r to %r\n' % (tmpname, realname))
420
420
421 if dirstate[realname] == 'n':
421 if dirstate[realname] == 'n':
422 # without normallookup, restoring timestamp
422 # without normallookup, restoring timestamp
423 # may cause partially committed files
423 # may cause partially committed files
424 # to be treated as unmodified
424 # to be treated as unmodified
425 dirstate.normallookup(realname)
425 dirstate.normallookup(realname)
426
426
427 # copystat=True here and above are a hack to trick any
427 # copystat=True here and above are a hack to trick any
428 # editors that have f open that we haven't modified them.
428 # editors that have f open that we haven't modified them.
429 #
429 #
430 # Also note that this racy as an editor could notice the
430 # Also note that this racy as an editor could notice the
431 # file's mtime before we've finished writing it.
431 # file's mtime before we've finished writing it.
432 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
432 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
433 os.unlink(tmpname)
433 os.unlink(tmpname)
434 if tobackup:
434 if tobackup:
435 os.rmdir(backupdir)
435 os.rmdir(backupdir)
436 except OSError:
436 except OSError:
437 pass
437 pass
438
438
439 def recordinwlock(ui, repo, message, match, opts):
439 def recordinwlock(ui, repo, message, match, opts):
440 with repo.wlock():
440 with repo.wlock():
441 return recordfunc(ui, repo, message, match, opts)
441 return recordfunc(ui, repo, message, match, opts)
442
442
443 return commit(ui, repo, recordinwlock, pats, opts)
443 return commit(ui, repo, recordinwlock, pats, opts)
444
444
445 class dirnode(object):
445 class dirnode(object):
446 """
446 """
447 Represent a directory in user working copy with information required for
447 Represent a directory in user working copy with information required for
448 the purpose of tersing its status.
448 the purpose of tersing its status.
449
449
450 path is the path to the directory, without a trailing '/'
450 path is the path to the directory, without a trailing '/'
451
451
452 statuses is a set of statuses of all files in this directory (this includes
452 statuses is a set of statuses of all files in this directory (this includes
453 all the files in all the subdirectories too)
453 all the files in all the subdirectories too)
454
454
455 files is a list of files which are direct child of this directory
455 files is a list of files which are direct child of this directory
456
456
457 subdirs is a dictionary of sub-directory name as the key and it's own
457 subdirs is a dictionary of sub-directory name as the key and it's own
458 dirnode object as the value
458 dirnode object as the value
459 """
459 """
460
460
461 def __init__(self, dirpath):
461 def __init__(self, dirpath):
462 self.path = dirpath
462 self.path = dirpath
463 self.statuses = set()
463 self.statuses = set()
464 self.files = []
464 self.files = []
465 self.subdirs = {}
465 self.subdirs = {}
466
466
467 def _addfileindir(self, filename, status):
467 def _addfileindir(self, filename, status):
468 """Add a file in this directory as a direct child."""
468 """Add a file in this directory as a direct child."""
469 self.files.append((filename, status))
469 self.files.append((filename, status))
470
470
471 def addfile(self, filename, status):
471 def addfile(self, filename, status):
472 """
472 """
473 Add a file to this directory or to its direct parent directory.
473 Add a file to this directory or to its direct parent directory.
474
474
475 If the file is not direct child of this directory, we traverse to the
475 If the file is not direct child of this directory, we traverse to the
476 directory of which this file is a direct child of and add the file
476 directory of which this file is a direct child of and add the file
477 there.
477 there.
478 """
478 """
479
479
480 # the filename contains a path separator, it means it's not the direct
480 # the filename contains a path separator, it means it's not the direct
481 # child of this directory
481 # child of this directory
482 if '/' in filename:
482 if '/' in filename:
483 subdir, filep = filename.split('/', 1)
483 subdir, filep = filename.split('/', 1)
484
484
485 # does the dirnode object for subdir exists
485 # does the dirnode object for subdir exists
486 if subdir not in self.subdirs:
486 if subdir not in self.subdirs:
487 subdirpath = pathutil.join(self.path, subdir)
487 subdirpath = pathutil.join(self.path, subdir)
488 self.subdirs[subdir] = dirnode(subdirpath)
488 self.subdirs[subdir] = dirnode(subdirpath)
489
489
490 # try adding the file in subdir
490 # try adding the file in subdir
491 self.subdirs[subdir].addfile(filep, status)
491 self.subdirs[subdir].addfile(filep, status)
492
492
493 else:
493 else:
494 self._addfileindir(filename, status)
494 self._addfileindir(filename, status)
495
495
496 if status not in self.statuses:
496 if status not in self.statuses:
497 self.statuses.add(status)
497 self.statuses.add(status)
498
498
499 def iterfilepaths(self):
499 def iterfilepaths(self):
500 """Yield (status, path) for files directly under this directory."""
500 """Yield (status, path) for files directly under this directory."""
501 for f, st in self.files:
501 for f, st in self.files:
502 yield st, pathutil.join(self.path, f)
502 yield st, pathutil.join(self.path, f)
503
503
504 def tersewalk(self, terseargs):
504 def tersewalk(self, terseargs):
505 """
505 """
506 Yield (status, path) obtained by processing the status of this
506 Yield (status, path) obtained by processing the status of this
507 dirnode.
507 dirnode.
508
508
509 terseargs is the string of arguments passed by the user with `--terse`
509 terseargs is the string of arguments passed by the user with `--terse`
510 flag.
510 flag.
511
511
512 Following are the cases which can happen:
512 Following are the cases which can happen:
513
513
514 1) All the files in the directory (including all the files in its
514 1) All the files in the directory (including all the files in its
515 subdirectories) share the same status and the user has asked us to terse
515 subdirectories) share the same status and the user has asked us to terse
516 that status. -> yield (status, dirpath). dirpath will end in '/'.
516 that status. -> yield (status, dirpath). dirpath will end in '/'.
517
517
518 2) Otherwise, we do following:
518 2) Otherwise, we do following:
519
519
520 a) Yield (status, filepath) for all the files which are in this
520 a) Yield (status, filepath) for all the files which are in this
521 directory (only the ones in this directory, not the subdirs)
521 directory (only the ones in this directory, not the subdirs)
522
522
523 b) Recurse the function on all the subdirectories of this
523 b) Recurse the function on all the subdirectories of this
524 directory
524 directory
525 """
525 """
526
526
527 if len(self.statuses) == 1:
527 if len(self.statuses) == 1:
528 onlyst = self.statuses.pop()
528 onlyst = self.statuses.pop()
529
529
530 # Making sure we terse only when the status abbreviation is
530 # Making sure we terse only when the status abbreviation is
531 # passed as terse argument
531 # passed as terse argument
532 if onlyst in terseargs:
532 if onlyst in terseargs:
533 yield onlyst, self.path + '/'
533 yield onlyst, self.path + '/'
534 return
534 return
535
535
536 # add the files to status list
536 # add the files to status list
537 for st, fpath in self.iterfilepaths():
537 for st, fpath in self.iterfilepaths():
538 yield st, fpath
538 yield st, fpath
539
539
540 #recurse on the subdirs
540 #recurse on the subdirs
541 for dirobj in self.subdirs.values():
541 for dirobj in self.subdirs.values():
542 for st, fpath in dirobj.tersewalk(terseargs):
542 for st, fpath in dirobj.tersewalk(terseargs):
543 yield st, fpath
543 yield st, fpath
544
544
545 def tersedir(statuslist, terseargs):
545 def tersedir(statuslist, terseargs):
546 """
546 """
547 Terse the status if all the files in a directory shares the same status.
547 Terse the status if all the files in a directory shares the same status.
548
548
549 statuslist is scmutil.status() object which contains a list of files for
549 statuslist is scmutil.status() object which contains a list of files for
550 each status.
550 each status.
551 terseargs is string which is passed by the user as the argument to `--terse`
551 terseargs is string which is passed by the user as the argument to `--terse`
552 flag.
552 flag.
553
553
554 The function makes a tree of objects of dirnode class, and at each node it
554 The function makes a tree of objects of dirnode class, and at each node it
555 stores the information required to know whether we can terse a certain
555 stores the information required to know whether we can terse a certain
556 directory or not.
556 directory or not.
557 """
557 """
558 # the order matters here as that is used to produce final list
558 # the order matters here as that is used to produce final list
559 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
559 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
560
560
561 # checking the argument validity
561 # checking the argument validity
562 for s in pycompat.bytestr(terseargs):
562 for s in pycompat.bytestr(terseargs):
563 if s not in allst:
563 if s not in allst:
564 raise error.Abort(_("'%s' not recognized") % s)
564 raise error.Abort(_("'%s' not recognized") % s)
565
565
566 # creating a dirnode object for the root of the repo
566 # creating a dirnode object for the root of the repo
567 rootobj = dirnode('')
567 rootobj = dirnode('')
568 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
568 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
569 'ignored', 'removed')
569 'ignored', 'removed')
570
570
571 tersedict = {}
571 tersedict = {}
572 for attrname in pstatus:
572 for attrname in pstatus:
573 statuschar = attrname[0:1]
573 statuschar = attrname[0:1]
574 for f in getattr(statuslist, attrname):
574 for f in getattr(statuslist, attrname):
575 rootobj.addfile(f, statuschar)
575 rootobj.addfile(f, statuschar)
576 tersedict[statuschar] = []
576 tersedict[statuschar] = []
577
577
578 # we won't be tersing the root dir, so add files in it
578 # we won't be tersing the root dir, so add files in it
579 for st, fpath in rootobj.iterfilepaths():
579 for st, fpath in rootobj.iterfilepaths():
580 tersedict[st].append(fpath)
580 tersedict[st].append(fpath)
581
581
582 # process each sub-directory and build tersedict
582 # process each sub-directory and build tersedict
583 for subdir in rootobj.subdirs.values():
583 for subdir in rootobj.subdirs.values():
584 for st, f in subdir.tersewalk(terseargs):
584 for st, f in subdir.tersewalk(terseargs):
585 tersedict[st].append(f)
585 tersedict[st].append(f)
586
586
587 tersedlist = []
587 tersedlist = []
588 for st in allst:
588 for st in allst:
589 tersedict[st].sort()
589 tersedict[st].sort()
590 tersedlist.append(tersedict[st])
590 tersedlist.append(tersedict[st])
591
591
592 return tersedlist
592 return tersedlist
593
593
594 def _commentlines(raw):
594 def _commentlines(raw):
595 '''Surround lineswith a comment char and a new line'''
595 '''Surround lineswith a comment char and a new line'''
596 lines = raw.splitlines()
596 lines = raw.splitlines()
597 commentedlines = ['# %s' % line for line in lines]
597 commentedlines = ['# %s' % line for line in lines]
598 return '\n'.join(commentedlines) + '\n'
598 return '\n'.join(commentedlines) + '\n'
599
599
600 def _conflictsmsg(repo):
600 def _conflictsmsg(repo):
601 mergestate = mergemod.mergestate.read(repo)
601 mergestate = mergemod.mergestate.read(repo)
602 if not mergestate.active():
602 if not mergestate.active():
603 return
603 return
604
604
605 m = scmutil.match(repo[None])
605 m = scmutil.match(repo[None])
606 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
606 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
607 if unresolvedlist:
607 if unresolvedlist:
608 mergeliststr = '\n'.join(
608 mergeliststr = '\n'.join(
609 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
609 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
610 for path in sorted(unresolvedlist)])
610 for path in sorted(unresolvedlist)])
611 msg = _('''Unresolved merge conflicts:
611 msg = _('''Unresolved merge conflicts:
612
612
613 %s
613 %s
614
614
615 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
615 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
616 else:
616 else:
617 msg = _('No unresolved merge conflicts.')
617 msg = _('No unresolved merge conflicts.')
618
618
619 return _commentlines(msg)
619 return _commentlines(msg)
620
620
621 def _helpmessage(continuecmd, abortcmd):
621 def _helpmessage(continuecmd, abortcmd):
622 msg = _('To continue: %s\n'
622 msg = _('To continue: %s\n'
623 'To abort: %s') % (continuecmd, abortcmd)
623 'To abort: %s') % (continuecmd, abortcmd)
624 return _commentlines(msg)
624 return _commentlines(msg)
625
625
626 def _rebasemsg():
626 def _rebasemsg():
627 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
627 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
628
628
629 def _histeditmsg():
629 def _histeditmsg():
630 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
630 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
631
631
632 def _unshelvemsg():
632 def _unshelvemsg():
633 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
633 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
634
634
635 def _graftmsg():
635 def _graftmsg():
636 return _helpmessage('hg graft --continue', 'hg graft --abort')
636 return _helpmessage('hg graft --continue', 'hg graft --abort')
637
637
638 def _mergemsg():
638 def _mergemsg():
639 return _helpmessage('hg commit', 'hg merge --abort')
639 return _helpmessage('hg commit', 'hg merge --abort')
640
640
641 def _bisectmsg():
641 def _bisectmsg():
642 msg = _('To mark the changeset good: hg bisect --good\n'
642 msg = _('To mark the changeset good: hg bisect --good\n'
643 'To mark the changeset bad: hg bisect --bad\n'
643 'To mark the changeset bad: hg bisect --bad\n'
644 'To abort: hg bisect --reset\n')
644 'To abort: hg bisect --reset\n')
645 return _commentlines(msg)
645 return _commentlines(msg)
646
646
647 def fileexistspredicate(filename):
647 def fileexistspredicate(filename):
648 return lambda repo: repo.vfs.exists(filename)
648 return lambda repo: repo.vfs.exists(filename)
649
649
650 def _mergepredicate(repo):
650 def _mergepredicate(repo):
651 return len(repo[None].parents()) > 1
651 return len(repo[None].parents()) > 1
652
652
653 STATES = (
653 STATES = (
654 # (state, predicate to detect states, helpful message function)
654 # (state, predicate to detect states, helpful message function)
655 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
655 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
656 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
656 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
657 ('graft', fileexistspredicate('graftstate'), _graftmsg),
657 ('graft', fileexistspredicate('graftstate'), _graftmsg),
658 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
658 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
659 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
659 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
660 # The merge state is part of a list that will be iterated over.
660 # The merge state is part of a list that will be iterated over.
661 # They need to be last because some of the other unfinished states may also
661 # They need to be last because some of the other unfinished states may also
662 # be in a merge or update state (eg. rebase, histedit, graft, etc).
662 # be in a merge or update state (eg. rebase, histedit, graft, etc).
663 # We want those to have priority.
663 # We want those to have priority.
664 ('merge', _mergepredicate, _mergemsg),
664 ('merge', _mergepredicate, _mergemsg),
665 )
665 )
666
666
667 def _getrepostate(repo):
667 def _getrepostate(repo):
668 # experimental config: commands.status.skipstates
668 # experimental config: commands.status.skipstates
669 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
669 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
670 for state, statedetectionpredicate, msgfn in STATES:
670 for state, statedetectionpredicate, msgfn in STATES:
671 if state in skip:
671 if state in skip:
672 continue
672 continue
673 if statedetectionpredicate(repo):
673 if statedetectionpredicate(repo):
674 return (state, statedetectionpredicate, msgfn)
674 return (state, statedetectionpredicate, msgfn)
675
675
676 def morestatus(repo, fm):
676 def morestatus(repo, fm):
677 statetuple = _getrepostate(repo)
677 statetuple = _getrepostate(repo)
678 label = 'status.morestatus'
678 label = 'status.morestatus'
679 if statetuple:
679 if statetuple:
680 state, statedetectionpredicate, helpfulmsg = statetuple
680 state, statedetectionpredicate, helpfulmsg = statetuple
681 statemsg = _('The repository is in an unfinished *%s* state.') % state
681 statemsg = _('The repository is in an unfinished *%s* state.') % state
682 fm.plain('%s\n' % _commentlines(statemsg), label=label)
682 fm.plain('%s\n' % _commentlines(statemsg), label=label)
683 conmsg = _conflictsmsg(repo)
683 conmsg = _conflictsmsg(repo)
684 if conmsg:
684 if conmsg:
685 fm.plain('%s\n' % conmsg, label=label)
685 fm.plain('%s\n' % conmsg, label=label)
686 if helpfulmsg:
686 if helpfulmsg:
687 helpmsg = helpfulmsg()
687 helpmsg = helpfulmsg()
688 fm.plain('%s\n' % helpmsg, label=label)
688 fm.plain('%s\n' % helpmsg, label=label)
689
689
690 def findpossible(cmd, table, strict=False):
690 def findpossible(cmd, table, strict=False):
691 """
691 """
692 Return cmd -> (aliases, command table entry)
692 Return cmd -> (aliases, command table entry)
693 for each matching command.
693 for each matching command.
694 Return debug commands (or their aliases) only if no normal command matches.
694 Return debug commands (or their aliases) only if no normal command matches.
695 """
695 """
696 choice = {}
696 choice = {}
697 debugchoice = {}
697 debugchoice = {}
698
698
699 if cmd in table:
699 if cmd in table:
700 # short-circuit exact matches, "log" alias beats "log|history"
700 # short-circuit exact matches, "log" alias beats "log|history"
701 keys = [cmd]
701 keys = [cmd]
702 else:
702 else:
703 keys = table.keys()
703 keys = table.keys()
704
704
705 allcmds = []
705 allcmds = []
706 for e in keys:
706 for e in keys:
707 aliases = parsealiases(e)
707 aliases = parsealiases(e)
708 allcmds.extend(aliases)
708 allcmds.extend(aliases)
709 found = None
709 found = None
710 if cmd in aliases:
710 if cmd in aliases:
711 found = cmd
711 found = cmd
712 elif not strict:
712 elif not strict:
713 for a in aliases:
713 for a in aliases:
714 if a.startswith(cmd):
714 if a.startswith(cmd):
715 found = a
715 found = a
716 break
716 break
717 if found is not None:
717 if found is not None:
718 if aliases[0].startswith("debug") or found.startswith("debug"):
718 if aliases[0].startswith("debug") or found.startswith("debug"):
719 debugchoice[found] = (aliases, table[e])
719 debugchoice[found] = (aliases, table[e])
720 else:
720 else:
721 choice[found] = (aliases, table[e])
721 choice[found] = (aliases, table[e])
722
722
723 if not choice and debugchoice:
723 if not choice and debugchoice:
724 choice = debugchoice
724 choice = debugchoice
725
725
726 return choice, allcmds
726 return choice, allcmds
727
727
728 def findcmd(cmd, table, strict=True):
728 def findcmd(cmd, table, strict=True):
729 """Return (aliases, command table entry) for command string."""
729 """Return (aliases, command table entry) for command string."""
730 choice, allcmds = findpossible(cmd, table, strict)
730 choice, allcmds = findpossible(cmd, table, strict)
731
731
732 if cmd in choice:
732 if cmd in choice:
733 return choice[cmd]
733 return choice[cmd]
734
734
735 if len(choice) > 1:
735 if len(choice) > 1:
736 clist = sorted(choice)
736 clist = sorted(choice)
737 raise error.AmbiguousCommand(cmd, clist)
737 raise error.AmbiguousCommand(cmd, clist)
738
738
739 if choice:
739 if choice:
740 return list(choice.values())[0]
740 return list(choice.values())[0]
741
741
742 raise error.UnknownCommand(cmd, allcmds)
742 raise error.UnknownCommand(cmd, allcmds)
743
743
744 def changebranch(ui, repo, revs, label):
744 def changebranch(ui, repo, revs, label):
745 """ Change the branch name of given revs to label """
745 """ Change the branch name of given revs to label """
746
746
747 with repo.wlock(), repo.lock(), repo.transaction('branches'):
747 with repo.wlock(), repo.lock(), repo.transaction('branches'):
748 # abort in case of uncommitted merge or dirty wdir
748 # abort in case of uncommitted merge or dirty wdir
749 bailifchanged(repo)
749 bailifchanged(repo)
750 revs = scmutil.revrange(repo, revs)
750 revs = scmutil.revrange(repo, revs)
751 if not revs:
751 if not revs:
752 raise error.Abort("empty revision set")
752 raise error.Abort("empty revision set")
753 roots = repo.revs('roots(%ld)', revs)
753 roots = repo.revs('roots(%ld)', revs)
754 if len(roots) > 1:
754 if len(roots) > 1:
755 raise error.Abort(_("cannot change branch of non-linear revisions"))
755 raise error.Abort(_("cannot change branch of non-linear revisions"))
756 rewriteutil.precheck(repo, revs, 'change branch of')
756 rewriteutil.precheck(repo, revs, 'change branch of')
757
757
758 root = repo[roots.first()]
758 root = repo[roots.first()]
759 rpb = {parent.branch() for parent in root.parents()}
759 rpb = {parent.branch() for parent in root.parents()}
760 if label not in rpb and label in repo.branchmap():
760 if label not in rpb and label in repo.branchmap():
761 raise error.Abort(_("a branch of the same name already exists"))
761 raise error.Abort(_("a branch of the same name already exists"))
762
762
763 if repo.revs('obsolete() and %ld', revs):
763 if repo.revs('obsolete() and %ld', revs):
764 raise error.Abort(_("cannot change branch of a obsolete changeset"))
764 raise error.Abort(_("cannot change branch of a obsolete changeset"))
765
765
766 # make sure only topological heads
766 # make sure only topological heads
767 if repo.revs('heads(%ld) - head()', revs):
767 if repo.revs('heads(%ld) - head()', revs):
768 raise error.Abort(_("cannot change branch in middle of a stack"))
768 raise error.Abort(_("cannot change branch in middle of a stack"))
769
769
770 replacements = {}
770 replacements = {}
771 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
771 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
772 # mercurial.subrepo -> mercurial.cmdutil
772 # mercurial.subrepo -> mercurial.cmdutil
773 from . import context
773 from . import context
774 for rev in revs:
774 for rev in revs:
775 ctx = repo[rev]
775 ctx = repo[rev]
776 oldbranch = ctx.branch()
776 oldbranch = ctx.branch()
777 # check if ctx has same branch
777 # check if ctx has same branch
778 if oldbranch == label:
778 if oldbranch == label:
779 continue
779 continue
780
780
781 def filectxfn(repo, newctx, path):
781 def filectxfn(repo, newctx, path):
782 try:
782 try:
783 return ctx[path]
783 return ctx[path]
784 except error.ManifestLookupError:
784 except error.ManifestLookupError:
785 return None
785 return None
786
786
787 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
787 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
788 % (hex(ctx.node()), oldbranch, label))
788 % (hex(ctx.node()), oldbranch, label))
789 extra = ctx.extra()
789 extra = ctx.extra()
790 extra['branch_change'] = hex(ctx.node())
790 extra['branch_change'] = hex(ctx.node())
791 # While changing branch of set of linear commits, make sure that
791 # While changing branch of set of linear commits, make sure that
792 # we base our commits on new parent rather than old parent which
792 # we base our commits on new parent rather than old parent which
793 # was obsoleted while changing the branch
793 # was obsoleted while changing the branch
794 p1 = ctx.p1().node()
794 p1 = ctx.p1().node()
795 p2 = ctx.p2().node()
795 p2 = ctx.p2().node()
796 if p1 in replacements:
796 if p1 in replacements:
797 p1 = replacements[p1][0]
797 p1 = replacements[p1][0]
798 if p2 in replacements:
798 if p2 in replacements:
799 p2 = replacements[p2][0]
799 p2 = replacements[p2][0]
800
800
801 mc = context.memctx(repo, (p1, p2),
801 mc = context.memctx(repo, (p1, p2),
802 ctx.description(),
802 ctx.description(),
803 ctx.files(),
803 ctx.files(),
804 filectxfn,
804 filectxfn,
805 user=ctx.user(),
805 user=ctx.user(),
806 date=ctx.date(),
806 date=ctx.date(),
807 extra=extra,
807 extra=extra,
808 branch=label)
808 branch=label)
809
809
810 newnode = repo.commitctx(mc)
810 newnode = repo.commitctx(mc)
811 replacements[ctx.node()] = (newnode,)
811 replacements[ctx.node()] = (newnode,)
812 ui.debug('new node id is %s\n' % hex(newnode))
812 ui.debug('new node id is %s\n' % hex(newnode))
813
813
814 # create obsmarkers and move bookmarks
814 # create obsmarkers and move bookmarks
815 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
815 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
816
816
817 # move the working copy too
817 # move the working copy too
818 wctx = repo[None]
818 wctx = repo[None]
819 # in-progress merge is a bit too complex for now.
819 # in-progress merge is a bit too complex for now.
820 if len(wctx.parents()) == 1:
820 if len(wctx.parents()) == 1:
821 newid = replacements.get(wctx.p1().node())
821 newid = replacements.get(wctx.p1().node())
822 if newid is not None:
822 if newid is not None:
823 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
823 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
824 # mercurial.cmdutil
824 # mercurial.cmdutil
825 from . import hg
825 from . import hg
826 hg.update(repo, newid[0], quietempty=True)
826 hg.update(repo, newid[0], quietempty=True)
827
827
828 ui.status(_("changed branch on %d changesets\n") % len(replacements))
828 ui.status(_("changed branch on %d changesets\n") % len(replacements))
829
829
830 def findrepo(p):
830 def findrepo(p):
831 while not os.path.isdir(os.path.join(p, ".hg")):
831 while not os.path.isdir(os.path.join(p, ".hg")):
832 oldp, p = p, os.path.dirname(p)
832 oldp, p = p, os.path.dirname(p)
833 if p == oldp:
833 if p == oldp:
834 return None
834 return None
835
835
836 return p
836 return p
837
837
838 def bailifchanged(repo, merge=True, hint=None):
838 def bailifchanged(repo, merge=True, hint=None):
839 """ enforce the precondition that working directory must be clean.
839 """ enforce the precondition that working directory must be clean.
840
840
841 'merge' can be set to false if a pending uncommitted merge should be
841 'merge' can be set to false if a pending uncommitted merge should be
842 ignored (such as when 'update --check' runs).
842 ignored (such as when 'update --check' runs).
843
843
844 'hint' is the usual hint given to Abort exception.
844 'hint' is the usual hint given to Abort exception.
845 """
845 """
846
846
847 if merge and repo.dirstate.p2() != nullid:
847 if merge and repo.dirstate.p2() != nullid:
848 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
848 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
849 modified, added, removed, deleted = repo.status()[:4]
849 modified, added, removed, deleted = repo.status()[:4]
850 if modified or added or removed or deleted:
850 if modified or added or removed or deleted:
851 raise error.Abort(_('uncommitted changes'), hint=hint)
851 raise error.Abort(_('uncommitted changes'), hint=hint)
852 ctx = repo[None]
852 ctx = repo[None]
853 for s in sorted(ctx.substate):
853 for s in sorted(ctx.substate):
854 ctx.sub(s).bailifchanged(hint=hint)
854 ctx.sub(s).bailifchanged(hint=hint)
855
855
856 def logmessage(ui, opts):
856 def logmessage(ui, opts):
857 """ get the log message according to -m and -l option """
857 """ get the log message according to -m and -l option """
858 message = opts.get('message')
858 message = opts.get('message')
859 logfile = opts.get('logfile')
859 logfile = opts.get('logfile')
860
860
861 if message and logfile:
861 if message and logfile:
862 raise error.Abort(_('options --message and --logfile are mutually '
862 raise error.Abort(_('options --message and --logfile are mutually '
863 'exclusive'))
863 'exclusive'))
864 if not message and logfile:
864 if not message and logfile:
865 try:
865 try:
866 if isstdiofilename(logfile):
866 if isstdiofilename(logfile):
867 message = ui.fin.read()
867 message = ui.fin.read()
868 else:
868 else:
869 message = '\n'.join(util.readfile(logfile).splitlines())
869 message = '\n'.join(util.readfile(logfile).splitlines())
870 except IOError as inst:
870 except IOError as inst:
871 raise error.Abort(_("can't read commit message '%s': %s") %
871 raise error.Abort(_("can't read commit message '%s': %s") %
872 (logfile, encoding.strtolocal(inst.strerror)))
872 (logfile, encoding.strtolocal(inst.strerror)))
873 return message
873 return message
874
874
875 def mergeeditform(ctxorbool, baseformname):
875 def mergeeditform(ctxorbool, baseformname):
876 """return appropriate editform name (referencing a committemplate)
876 """return appropriate editform name (referencing a committemplate)
877
877
878 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
878 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
879 merging is committed.
879 merging is committed.
880
880
881 This returns baseformname with '.merge' appended if it is a merge,
881 This returns baseformname with '.merge' appended if it is a merge,
882 otherwise '.normal' is appended.
882 otherwise '.normal' is appended.
883 """
883 """
884 if isinstance(ctxorbool, bool):
884 if isinstance(ctxorbool, bool):
885 if ctxorbool:
885 if ctxorbool:
886 return baseformname + ".merge"
886 return baseformname + ".merge"
887 elif len(ctxorbool.parents()) > 1:
887 elif len(ctxorbool.parents()) > 1:
888 return baseformname + ".merge"
888 return baseformname + ".merge"
889
889
890 return baseformname + ".normal"
890 return baseformname + ".normal"
891
891
892 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
892 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
893 editform='', **opts):
893 editform='', **opts):
894 """get appropriate commit message editor according to '--edit' option
894 """get appropriate commit message editor according to '--edit' option
895
895
896 'finishdesc' is a function to be called with edited commit message
896 'finishdesc' is a function to be called with edited commit message
897 (= 'description' of the new changeset) just after editing, but
897 (= 'description' of the new changeset) just after editing, but
898 before checking empty-ness. It should return actual text to be
898 before checking empty-ness. It should return actual text to be
899 stored into history. This allows to change description before
899 stored into history. This allows to change description before
900 storing.
900 storing.
901
901
902 'extramsg' is a extra message to be shown in the editor instead of
902 'extramsg' is a extra message to be shown in the editor instead of
903 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
903 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
904 is automatically added.
904 is automatically added.
905
905
906 'editform' is a dot-separated list of names, to distinguish
906 'editform' is a dot-separated list of names, to distinguish
907 the purpose of commit text editing.
907 the purpose of commit text editing.
908
908
909 'getcommiteditor' returns 'commitforceeditor' regardless of
909 'getcommiteditor' returns 'commitforceeditor' regardless of
910 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
910 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
911 they are specific for usage in MQ.
911 they are specific for usage in MQ.
912 """
912 """
913 if edit or finishdesc or extramsg:
913 if edit or finishdesc or extramsg:
914 return lambda r, c, s: commitforceeditor(r, c, s,
914 return lambda r, c, s: commitforceeditor(r, c, s,
915 finishdesc=finishdesc,
915 finishdesc=finishdesc,
916 extramsg=extramsg,
916 extramsg=extramsg,
917 editform=editform)
917 editform=editform)
918 elif editform:
918 elif editform:
919 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
919 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
920 else:
920 else:
921 return commiteditor
921 return commiteditor
922
922
923 def _escapecommandtemplate(tmpl):
923 def _escapecommandtemplate(tmpl):
924 parts = []
924 parts = []
925 for typ, start, end in templater.scantemplate(tmpl, raw=True):
925 for typ, start, end in templater.scantemplate(tmpl, raw=True):
926 if typ == b'string':
926 if typ == b'string':
927 parts.append(stringutil.escapestr(tmpl[start:end]))
927 parts.append(stringutil.escapestr(tmpl[start:end]))
928 else:
928 else:
929 parts.append(tmpl[start:end])
929 parts.append(tmpl[start:end])
930 return b''.join(parts)
930 return b''.join(parts)
931
931
932 def rendercommandtemplate(ui, tmpl, props):
932 def rendercommandtemplate(ui, tmpl, props):
933 r"""Expand a literal template 'tmpl' in a way suitable for command line
933 r"""Expand a literal template 'tmpl' in a way suitable for command line
934
934
935 '\' in outermost string is not taken as an escape character because it
935 '\' in outermost string is not taken as an escape character because it
936 is a directory separator on Windows.
936 is a directory separator on Windows.
937
937
938 >>> from . import ui as uimod
938 >>> from . import ui as uimod
939 >>> ui = uimod.ui()
939 >>> ui = uimod.ui()
940 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
940 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
941 'c:\\foo'
941 'c:\\foo'
942 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
942 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
943 'c:{path}'
943 'c:{path}'
944 """
944 """
945 if not tmpl:
945 if not tmpl:
946 return tmpl
946 return tmpl
947 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
947 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
948 return t.renderdefault(props)
948 return t.renderdefault(props)
949
949
950 def rendertemplate(ctx, tmpl, props=None):
950 def rendertemplate(ctx, tmpl, props=None):
951 """Expand a literal template 'tmpl' byte-string against one changeset
951 """Expand a literal template 'tmpl' byte-string against one changeset
952
952
953 Each props item must be a stringify-able value or a callable returning
953 Each props item must be a stringify-able value or a callable returning
954 such value, i.e. no bare list nor dict should be passed.
954 such value, i.e. no bare list nor dict should be passed.
955 """
955 """
956 repo = ctx.repo()
956 repo = ctx.repo()
957 tres = formatter.templateresources(repo.ui, repo)
957 tres = formatter.templateresources(repo.ui, repo)
958 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
958 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
959 resources=tres)
959 resources=tres)
960 mapping = {'ctx': ctx}
960 mapping = {'ctx': ctx}
961 if props:
961 if props:
962 mapping.update(props)
962 mapping.update(props)
963 return t.renderdefault(mapping)
963 return t.renderdefault(mapping)
964
964
965 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
965 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
966 r"""Convert old-style filename format string to template string
966 r"""Convert old-style filename format string to template string
967
967
968 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
968 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
969 'foo-{reporoot|basename}-{seqno}.patch'
969 'foo-{reporoot|basename}-{seqno}.patch'
970 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
970 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
971 '{rev}{tags % "{tag}"}{node}'
971 '{rev}{tags % "{tag}"}{node}'
972
972
973 '\' in outermost strings has to be escaped because it is a directory
973 '\' in outermost strings has to be escaped because it is a directory
974 separator on Windows:
974 separator on Windows:
975
975
976 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
976 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
977 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
977 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
978 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
978 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
979 '\\\\\\\\foo\\\\bar.patch'
979 '\\\\\\\\foo\\\\bar.patch'
980 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
980 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
981 '\\\\{tags % "{tag}"}'
981 '\\\\{tags % "{tag}"}'
982
982
983 but inner strings follow the template rules (i.e. '\' is taken as an
983 but inner strings follow the template rules (i.e. '\' is taken as an
984 escape character):
984 escape character):
985
985
986 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
986 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
987 '{"c:\\tmp"}'
987 '{"c:\\tmp"}'
988 """
988 """
989 expander = {
989 expander = {
990 b'H': b'{node}',
990 b'H': b'{node}',
991 b'R': b'{rev}',
991 b'R': b'{rev}',
992 b'h': b'{node|short}',
992 b'h': b'{node|short}',
993 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
993 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
994 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
994 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
995 b'%': b'%',
995 b'%': b'%',
996 b'b': b'{reporoot|basename}',
996 b'b': b'{reporoot|basename}',
997 }
997 }
998 if total is not None:
998 if total is not None:
999 expander[b'N'] = b'{total}'
999 expander[b'N'] = b'{total}'
1000 if seqno is not None:
1000 if seqno is not None:
1001 expander[b'n'] = b'{seqno}'
1001 expander[b'n'] = b'{seqno}'
1002 if total is not None and seqno is not None:
1002 if total is not None and seqno is not None:
1003 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1003 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1004 if pathname is not None:
1004 if pathname is not None:
1005 expander[b's'] = b'{pathname|basename}'
1005 expander[b's'] = b'{pathname|basename}'
1006 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1006 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1007 expander[b'p'] = b'{pathname}'
1007 expander[b'p'] = b'{pathname}'
1008
1008
1009 newname = []
1009 newname = []
1010 for typ, start, end in templater.scantemplate(pat, raw=True):
1010 for typ, start, end in templater.scantemplate(pat, raw=True):
1011 if typ != b'string':
1011 if typ != b'string':
1012 newname.append(pat[start:end])
1012 newname.append(pat[start:end])
1013 continue
1013 continue
1014 i = start
1014 i = start
1015 while i < end:
1015 while i < end:
1016 n = pat.find(b'%', i, end)
1016 n = pat.find(b'%', i, end)
1017 if n < 0:
1017 if n < 0:
1018 newname.append(stringutil.escapestr(pat[i:end]))
1018 newname.append(stringutil.escapestr(pat[i:end]))
1019 break
1019 break
1020 newname.append(stringutil.escapestr(pat[i:n]))
1020 newname.append(stringutil.escapestr(pat[i:n]))
1021 if n + 2 > end:
1021 if n + 2 > end:
1022 raise error.Abort(_("incomplete format spec in output "
1022 raise error.Abort(_("incomplete format spec in output "
1023 "filename"))
1023 "filename"))
1024 c = pat[n + 1:n + 2]
1024 c = pat[n + 1:n + 2]
1025 i = n + 2
1025 i = n + 2
1026 try:
1026 try:
1027 newname.append(expander[c])
1027 newname.append(expander[c])
1028 except KeyError:
1028 except KeyError:
1029 raise error.Abort(_("invalid format spec '%%%s' in output "
1029 raise error.Abort(_("invalid format spec '%%%s' in output "
1030 "filename") % c)
1030 "filename") % c)
1031 return ''.join(newname)
1031 return ''.join(newname)
1032
1032
1033 def makefilename(ctx, pat, **props):
1033 def makefilename(ctx, pat, **props):
1034 if not pat:
1034 if not pat:
1035 return pat
1035 return pat
1036 tmpl = _buildfntemplate(pat, **props)
1036 tmpl = _buildfntemplate(pat, **props)
1037 # BUG: alias expansion shouldn't be made against template fragments
1037 # BUG: alias expansion shouldn't be made against template fragments
1038 # rewritten from %-format strings, but we have no easy way to partially
1038 # rewritten from %-format strings, but we have no easy way to partially
1039 # disable the expansion.
1039 # disable the expansion.
1040 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1040 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1041
1041
1042 def isstdiofilename(pat):
1042 def isstdiofilename(pat):
1043 """True if the given pat looks like a filename denoting stdin/stdout"""
1043 """True if the given pat looks like a filename denoting stdin/stdout"""
1044 return not pat or pat == '-'
1044 return not pat or pat == '-'
1045
1045
1046 class _unclosablefile(object):
1046 class _unclosablefile(object):
1047 def __init__(self, fp):
1047 def __init__(self, fp):
1048 self._fp = fp
1048 self._fp = fp
1049
1049
1050 def close(self):
1050 def close(self):
1051 pass
1051 pass
1052
1052
1053 def __iter__(self):
1053 def __iter__(self):
1054 return iter(self._fp)
1054 return iter(self._fp)
1055
1055
1056 def __getattr__(self, attr):
1056 def __getattr__(self, attr):
1057 return getattr(self._fp, attr)
1057 return getattr(self._fp, attr)
1058
1058
1059 def __enter__(self):
1059 def __enter__(self):
1060 return self
1060 return self
1061
1061
1062 def __exit__(self, exc_type, exc_value, exc_tb):
1062 def __exit__(self, exc_type, exc_value, exc_tb):
1063 pass
1063 pass
1064
1064
1065 def makefileobj(ctx, pat, mode='wb', **props):
1065 def makefileobj(ctx, pat, mode='wb', **props):
1066 writable = mode not in ('r', 'rb')
1066 writable = mode not in ('r', 'rb')
1067
1067
1068 if isstdiofilename(pat):
1068 if isstdiofilename(pat):
1069 repo = ctx.repo()
1069 repo = ctx.repo()
1070 if writable:
1070 if writable:
1071 fp = repo.ui.fout
1071 fp = repo.ui.fout
1072 else:
1072 else:
1073 fp = repo.ui.fin
1073 fp = repo.ui.fin
1074 return _unclosablefile(fp)
1074 return _unclosablefile(fp)
1075 fn = makefilename(ctx, pat, **props)
1075 fn = makefilename(ctx, pat, **props)
1076 return open(fn, mode)
1076 return open(fn, mode)
1077
1077
1078 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1078 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1079 """opens the changelog, manifest, a filelog or a given revlog"""
1079 """opens the changelog, manifest, a filelog or a given revlog"""
1080 cl = opts['changelog']
1080 cl = opts['changelog']
1081 mf = opts['manifest']
1081 mf = opts['manifest']
1082 dir = opts['dir']
1082 dir = opts['dir']
1083 msg = None
1083 msg = None
1084 if cl and mf:
1084 if cl and mf:
1085 msg = _('cannot specify --changelog and --manifest at the same time')
1085 msg = _('cannot specify --changelog and --manifest at the same time')
1086 elif cl and dir:
1086 elif cl and dir:
1087 msg = _('cannot specify --changelog and --dir at the same time')
1087 msg = _('cannot specify --changelog and --dir at the same time')
1088 elif cl or mf or dir:
1088 elif cl or mf or dir:
1089 if file_:
1089 if file_:
1090 msg = _('cannot specify filename with --changelog or --manifest')
1090 msg = _('cannot specify filename with --changelog or --manifest')
1091 elif not repo:
1091 elif not repo:
1092 msg = _('cannot specify --changelog or --manifest or --dir '
1092 msg = _('cannot specify --changelog or --manifest or --dir '
1093 'without a repository')
1093 'without a repository')
1094 if msg:
1094 if msg:
1095 raise error.Abort(msg)
1095 raise error.Abort(msg)
1096
1096
1097 r = None
1097 r = None
1098 if repo:
1098 if repo:
1099 if cl:
1099 if cl:
1100 r = repo.unfiltered().changelog
1100 r = repo.unfiltered().changelog
1101 elif dir:
1101 elif dir:
1102 if 'treemanifest' not in repo.requirements:
1102 if 'treemanifest' not in repo.requirements:
1103 raise error.Abort(_("--dir can only be used on repos with "
1103 raise error.Abort(_("--dir can only be used on repos with "
1104 "treemanifest enabled"))
1104 "treemanifest enabled"))
1105 if not dir.endswith('/'):
1105 if not dir.endswith('/'):
1106 dir = dir + '/'
1106 dir = dir + '/'
1107 dirlog = repo.manifestlog.getstorage(dir)
1107 dirlog = repo.manifestlog.getstorage(dir)
1108 if len(dirlog):
1108 if len(dirlog):
1109 r = dirlog
1109 r = dirlog
1110 elif mf:
1110 elif mf:
1111 r = repo.manifestlog.getstorage(b'')
1111 r = repo.manifestlog.getstorage(b'')
1112 elif file_:
1112 elif file_:
1113 filelog = repo.file(file_)
1113 filelog = repo.file(file_)
1114 if len(filelog):
1114 if len(filelog):
1115 r = filelog
1115 r = filelog
1116
1116
1117 # Not all storage may be revlogs. If requested, try to return an actual
1117 # Not all storage may be revlogs. If requested, try to return an actual
1118 # revlog instance.
1118 # revlog instance.
1119 if returnrevlog:
1119 if returnrevlog:
1120 if isinstance(r, revlog.revlog):
1120 if isinstance(r, revlog.revlog):
1121 pass
1121 pass
1122 elif util.safehasattr(r, '_revlog'):
1122 elif util.safehasattr(r, '_revlog'):
1123 r = r._revlog
1123 r = r._revlog
1124 elif r is not None:
1124 elif r is not None:
1125 raise error.Abort(_('%r does not appear to be a revlog') % r)
1125 raise error.Abort(_('%r does not appear to be a revlog') % r)
1126
1126
1127 if not r:
1127 if not r:
1128 if not returnrevlog:
1128 if not returnrevlog:
1129 raise error.Abort(_('cannot give path to non-revlog'))
1129 raise error.Abort(_('cannot give path to non-revlog'))
1130
1130
1131 if not file_:
1131 if not file_:
1132 raise error.CommandError(cmd, _('invalid arguments'))
1132 raise error.CommandError(cmd, _('invalid arguments'))
1133 if not os.path.isfile(file_):
1133 if not os.path.isfile(file_):
1134 raise error.Abort(_("revlog '%s' not found") % file_)
1134 raise error.Abort(_("revlog '%s' not found") % file_)
1135 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1135 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1136 file_[:-2] + ".i")
1136 file_[:-2] + ".i")
1137 return r
1137 return r
1138
1138
1139 def openrevlog(repo, cmd, file_, opts):
1139 def openrevlog(repo, cmd, file_, opts):
1140 """Obtain a revlog backing storage of an item.
1140 """Obtain a revlog backing storage of an item.
1141
1141
1142 This is similar to ``openstorage()`` except it always returns a revlog.
1142 This is similar to ``openstorage()`` except it always returns a revlog.
1143
1143
1144 In most cases, a caller cares about the main storage object - not the
1144 In most cases, a caller cares about the main storage object - not the
1145 revlog backing it. Therefore, this function should only be used by code
1145 revlog backing it. Therefore, this function should only be used by code
1146 that needs to examine low-level revlog implementation details. e.g. debug
1146 that needs to examine low-level revlog implementation details. e.g. debug
1147 commands.
1147 commands.
1148 """
1148 """
1149 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1149 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1150
1150
1151 def copy(ui, repo, pats, opts, rename=False):
1151 def copy(ui, repo, pats, opts, rename=False):
1152 # called with the repo lock held
1152 # called with the repo lock held
1153 #
1153 #
1154 # hgsep => pathname that uses "/" to separate directories
1154 # hgsep => pathname that uses "/" to separate directories
1155 # ossep => pathname that uses os.sep to separate directories
1155 # ossep => pathname that uses os.sep to separate directories
1156 cwd = repo.getcwd()
1156 cwd = repo.getcwd()
1157 targets = {}
1157 targets = {}
1158 after = opts.get("after")
1158 after = opts.get("after")
1159 dryrun = opts.get("dry_run")
1159 dryrun = opts.get("dry_run")
1160 wctx = repo[None]
1160 wctx = repo[None]
1161
1161
1162 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1162 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1163 def walkpat(pat):
1163 def walkpat(pat):
1164 srcs = []
1164 srcs = []
1165 if after:
1165 if after:
1166 badstates = '?'
1166 badstates = '?'
1167 else:
1167 else:
1168 badstates = '?r'
1168 badstates = '?r'
1169 m = scmutil.match(wctx, [pat], opts, globbed=True)
1169 m = scmutil.match(wctx, [pat], opts, globbed=True)
1170 for abs in wctx.walk(m):
1170 for abs in wctx.walk(m):
1171 state = repo.dirstate[abs]
1171 state = repo.dirstate[abs]
1172 rel = uipathfn(abs)
1172 rel = uipathfn(abs)
1173 exact = m.exact(abs)
1173 exact = m.exact(abs)
1174 if state in badstates:
1174 if state in badstates:
1175 if exact and state == '?':
1175 if exact and state == '?':
1176 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1176 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1177 if exact and state == 'r':
1177 if exact and state == 'r':
1178 ui.warn(_('%s: not copying - file has been marked for'
1178 ui.warn(_('%s: not copying - file has been marked for'
1179 ' remove\n') % rel)
1179 ' remove\n') % rel)
1180 continue
1180 continue
1181 # abs: hgsep
1181 # abs: hgsep
1182 # rel: ossep
1182 # rel: ossep
1183 srcs.append((abs, rel, exact))
1183 srcs.append((abs, rel, exact))
1184 return srcs
1184 return srcs
1185
1185
1186 # abssrc: hgsep
1186 # abssrc: hgsep
1187 # relsrc: ossep
1187 # relsrc: ossep
1188 # otarget: ossep
1188 # otarget: ossep
1189 def copyfile(abssrc, relsrc, otarget, exact):
1189 def copyfile(abssrc, relsrc, otarget, exact):
1190 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1190 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1191 if '/' in abstarget:
1191 if '/' in abstarget:
1192 # We cannot normalize abstarget itself, this would prevent
1192 # We cannot normalize abstarget itself, this would prevent
1193 # case only renames, like a => A.
1193 # case only renames, like a => A.
1194 abspath, absname = abstarget.rsplit('/', 1)
1194 abspath, absname = abstarget.rsplit('/', 1)
1195 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1195 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1196 reltarget = repo.pathto(abstarget, cwd)
1196 reltarget = repo.pathto(abstarget, cwd)
1197 target = repo.wjoin(abstarget)
1197 target = repo.wjoin(abstarget)
1198 src = repo.wjoin(abssrc)
1198 src = repo.wjoin(abssrc)
1199 state = repo.dirstate[abstarget]
1199 state = repo.dirstate[abstarget]
1200
1200
1201 scmutil.checkportable(ui, abstarget)
1201 scmutil.checkportable(ui, abstarget)
1202
1202
1203 # check for collisions
1203 # check for collisions
1204 prevsrc = targets.get(abstarget)
1204 prevsrc = targets.get(abstarget)
1205 if prevsrc is not None:
1205 if prevsrc is not None:
1206 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1206 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1207 (reltarget, repo.pathto(abssrc, cwd),
1207 (reltarget, repo.pathto(abssrc, cwd),
1208 repo.pathto(prevsrc, cwd)))
1208 repo.pathto(prevsrc, cwd)))
1209 return True # report a failure
1209 return True # report a failure
1210
1210
1211 # check for overwrites
1211 # check for overwrites
1212 exists = os.path.lexists(target)
1212 exists = os.path.lexists(target)
1213 samefile = False
1213 samefile = False
1214 if exists and abssrc != abstarget:
1214 if exists and abssrc != abstarget:
1215 if (repo.dirstate.normalize(abssrc) ==
1215 if (repo.dirstate.normalize(abssrc) ==
1216 repo.dirstate.normalize(abstarget)):
1216 repo.dirstate.normalize(abstarget)):
1217 if not rename:
1217 if not rename:
1218 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1218 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1219 return True # report a failure
1219 return True # report a failure
1220 exists = False
1220 exists = False
1221 samefile = True
1221 samefile = True
1222
1222
1223 if not after and exists or after and state in 'mn':
1223 if not after and exists or after and state in 'mn':
1224 if not opts['force']:
1224 if not opts['force']:
1225 if state in 'mn':
1225 if state in 'mn':
1226 msg = _('%s: not overwriting - file already committed\n')
1226 msg = _('%s: not overwriting - file already committed\n')
1227 if after:
1227 if after:
1228 flags = '--after --force'
1228 flags = '--after --force'
1229 else:
1229 else:
1230 flags = '--force'
1230 flags = '--force'
1231 if rename:
1231 if rename:
1232 hint = _("('hg rename %s' to replace the file by "
1232 hint = _("('hg rename %s' to replace the file by "
1233 'recording a rename)\n') % flags
1233 'recording a rename)\n') % flags
1234 else:
1234 else:
1235 hint = _("('hg copy %s' to replace the file by "
1235 hint = _("('hg copy %s' to replace the file by "
1236 'recording a copy)\n') % flags
1236 'recording a copy)\n') % flags
1237 else:
1237 else:
1238 msg = _('%s: not overwriting - file exists\n')
1238 msg = _('%s: not overwriting - file exists\n')
1239 if rename:
1239 if rename:
1240 hint = _("('hg rename --after' to record the rename)\n")
1240 hint = _("('hg rename --after' to record the rename)\n")
1241 else:
1241 else:
1242 hint = _("('hg copy --after' to record the copy)\n")
1242 hint = _("('hg copy --after' to record the copy)\n")
1243 ui.warn(msg % reltarget)
1243 ui.warn(msg % reltarget)
1244 ui.warn(hint)
1244 ui.warn(hint)
1245 return True # report a failure
1245 return True # report a failure
1246
1246
1247 if after:
1247 if after:
1248 if not exists:
1248 if not exists:
1249 if rename:
1249 if rename:
1250 ui.warn(_('%s: not recording move - %s does not exist\n') %
1250 ui.warn(_('%s: not recording move - %s does not exist\n') %
1251 (relsrc, reltarget))
1251 (relsrc, reltarget))
1252 else:
1252 else:
1253 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1253 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1254 (relsrc, reltarget))
1254 (relsrc, reltarget))
1255 return True # report a failure
1255 return True # report a failure
1256 elif not dryrun:
1256 elif not dryrun:
1257 try:
1257 try:
1258 if exists:
1258 if exists:
1259 os.unlink(target)
1259 os.unlink(target)
1260 targetdir = os.path.dirname(target) or '.'
1260 targetdir = os.path.dirname(target) or '.'
1261 if not os.path.isdir(targetdir):
1261 if not os.path.isdir(targetdir):
1262 os.makedirs(targetdir)
1262 os.makedirs(targetdir)
1263 if samefile:
1263 if samefile:
1264 tmp = target + "~hgrename"
1264 tmp = target + "~hgrename"
1265 os.rename(src, tmp)
1265 os.rename(src, tmp)
1266 os.rename(tmp, target)
1266 os.rename(tmp, target)
1267 else:
1267 else:
1268 # Preserve stat info on renames, not on copies; this matches
1268 # Preserve stat info on renames, not on copies; this matches
1269 # Linux CLI behavior.
1269 # Linux CLI behavior.
1270 util.copyfile(src, target, copystat=rename)
1270 util.copyfile(src, target, copystat=rename)
1271 srcexists = True
1271 srcexists = True
1272 except IOError as inst:
1272 except IOError as inst:
1273 if inst.errno == errno.ENOENT:
1273 if inst.errno == errno.ENOENT:
1274 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1274 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1275 srcexists = False
1275 srcexists = False
1276 else:
1276 else:
1277 ui.warn(_('%s: cannot copy - %s\n') %
1277 ui.warn(_('%s: cannot copy - %s\n') %
1278 (relsrc, encoding.strtolocal(inst.strerror)))
1278 (relsrc, encoding.strtolocal(inst.strerror)))
1279 return True # report a failure
1279 return True # report a failure
1280
1280
1281 if ui.verbose or not exact:
1281 if ui.verbose or not exact:
1282 if rename:
1282 if rename:
1283 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1283 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1284 else:
1284 else:
1285 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1285 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1286
1286
1287 targets[abstarget] = abssrc
1287 targets[abstarget] = abssrc
1288
1288
1289 # fix up dirstate
1289 # fix up dirstate
1290 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1290 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1291 dryrun=dryrun, cwd=cwd)
1291 dryrun=dryrun, cwd=cwd)
1292 if rename and not dryrun:
1292 if rename and not dryrun:
1293 if not after and srcexists and not samefile:
1293 if not after and srcexists and not samefile:
1294 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1294 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1295 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1295 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1296 wctx.forget([abssrc])
1296 wctx.forget([abssrc])
1297
1297
1298 # pat: ossep
1298 # pat: ossep
1299 # dest ossep
1299 # dest ossep
1300 # srcs: list of (hgsep, hgsep, ossep, bool)
1300 # srcs: list of (hgsep, hgsep, ossep, bool)
1301 # return: function that takes hgsep and returns ossep
1301 # return: function that takes hgsep and returns ossep
1302 def targetpathfn(pat, dest, srcs):
1302 def targetpathfn(pat, dest, srcs):
1303 if os.path.isdir(pat):
1303 if os.path.isdir(pat):
1304 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1304 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1305 abspfx = util.localpath(abspfx)
1305 abspfx = util.localpath(abspfx)
1306 if destdirexists:
1306 if destdirexists:
1307 striplen = len(os.path.split(abspfx)[0])
1307 striplen = len(os.path.split(abspfx)[0])
1308 else:
1308 else:
1309 striplen = len(abspfx)
1309 striplen = len(abspfx)
1310 if striplen:
1310 if striplen:
1311 striplen += len(pycompat.ossep)
1311 striplen += len(pycompat.ossep)
1312 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1312 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1313 elif destdirexists:
1313 elif destdirexists:
1314 res = lambda p: os.path.join(dest,
1314 res = lambda p: os.path.join(dest,
1315 os.path.basename(util.localpath(p)))
1315 os.path.basename(util.localpath(p)))
1316 else:
1316 else:
1317 res = lambda p: dest
1317 res = lambda p: dest
1318 return res
1318 return res
1319
1319
1320 # pat: ossep
1320 # pat: ossep
1321 # dest ossep
1321 # dest ossep
1322 # srcs: list of (hgsep, hgsep, ossep, bool)
1322 # srcs: list of (hgsep, hgsep, ossep, bool)
1323 # return: function that takes hgsep and returns ossep
1323 # return: function that takes hgsep and returns ossep
1324 def targetpathafterfn(pat, dest, srcs):
1324 def targetpathafterfn(pat, dest, srcs):
1325 if matchmod.patkind(pat):
1325 if matchmod.patkind(pat):
1326 # a mercurial pattern
1326 # a mercurial pattern
1327 res = lambda p: os.path.join(dest,
1327 res = lambda p: os.path.join(dest,
1328 os.path.basename(util.localpath(p)))
1328 os.path.basename(util.localpath(p)))
1329 else:
1329 else:
1330 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1330 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1331 if len(abspfx) < len(srcs[0][0]):
1331 if len(abspfx) < len(srcs[0][0]):
1332 # A directory. Either the target path contains the last
1332 # A directory. Either the target path contains the last
1333 # component of the source path or it does not.
1333 # component of the source path or it does not.
1334 def evalpath(striplen):
1334 def evalpath(striplen):
1335 score = 0
1335 score = 0
1336 for s in srcs:
1336 for s in srcs:
1337 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1337 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1338 if os.path.lexists(t):
1338 if os.path.lexists(t):
1339 score += 1
1339 score += 1
1340 return score
1340 return score
1341
1341
1342 abspfx = util.localpath(abspfx)
1342 abspfx = util.localpath(abspfx)
1343 striplen = len(abspfx)
1343 striplen = len(abspfx)
1344 if striplen:
1344 if striplen:
1345 striplen += len(pycompat.ossep)
1345 striplen += len(pycompat.ossep)
1346 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1346 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1347 score = evalpath(striplen)
1347 score = evalpath(striplen)
1348 striplen1 = len(os.path.split(abspfx)[0])
1348 striplen1 = len(os.path.split(abspfx)[0])
1349 if striplen1:
1349 if striplen1:
1350 striplen1 += len(pycompat.ossep)
1350 striplen1 += len(pycompat.ossep)
1351 if evalpath(striplen1) > score:
1351 if evalpath(striplen1) > score:
1352 striplen = striplen1
1352 striplen = striplen1
1353 res = lambda p: os.path.join(dest,
1353 res = lambda p: os.path.join(dest,
1354 util.localpath(p)[striplen:])
1354 util.localpath(p)[striplen:])
1355 else:
1355 else:
1356 # a file
1356 # a file
1357 if destdirexists:
1357 if destdirexists:
1358 res = lambda p: os.path.join(dest,
1358 res = lambda p: os.path.join(dest,
1359 os.path.basename(util.localpath(p)))
1359 os.path.basename(util.localpath(p)))
1360 else:
1360 else:
1361 res = lambda p: dest
1361 res = lambda p: dest
1362 return res
1362 return res
1363
1363
1364 pats = scmutil.expandpats(pats)
1364 pats = scmutil.expandpats(pats)
1365 if not pats:
1365 if not pats:
1366 raise error.Abort(_('no source or destination specified'))
1366 raise error.Abort(_('no source or destination specified'))
1367 if len(pats) == 1:
1367 if len(pats) == 1:
1368 raise error.Abort(_('no destination specified'))
1368 raise error.Abort(_('no destination specified'))
1369 dest = pats.pop()
1369 dest = pats.pop()
1370 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1370 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1371 if not destdirexists:
1371 if not destdirexists:
1372 if len(pats) > 1 or matchmod.patkind(pats[0]):
1372 if len(pats) > 1 or matchmod.patkind(pats[0]):
1373 raise error.Abort(_('with multiple sources, destination must be an '
1373 raise error.Abort(_('with multiple sources, destination must be an '
1374 'existing directory'))
1374 'existing directory'))
1375 if util.endswithsep(dest):
1375 if util.endswithsep(dest):
1376 raise error.Abort(_('destination %s is not a directory') % dest)
1376 raise error.Abort(_('destination %s is not a directory') % dest)
1377
1377
1378 tfn = targetpathfn
1378 tfn = targetpathfn
1379 if after:
1379 if after:
1380 tfn = targetpathafterfn
1380 tfn = targetpathafterfn
1381 copylist = []
1381 copylist = []
1382 for pat in pats:
1382 for pat in pats:
1383 srcs = walkpat(pat)
1383 srcs = walkpat(pat)
1384 if not srcs:
1384 if not srcs:
1385 continue
1385 continue
1386 copylist.append((tfn(pat, dest, srcs), srcs))
1386 copylist.append((tfn(pat, dest, srcs), srcs))
1387 if not copylist:
1387 if not copylist:
1388 raise error.Abort(_('no files to copy'))
1388 raise error.Abort(_('no files to copy'))
1389
1389
1390 errors = 0
1390 errors = 0
1391 for targetpath, srcs in copylist:
1391 for targetpath, srcs in copylist:
1392 for abssrc, relsrc, exact in srcs:
1392 for abssrc, relsrc, exact in srcs:
1393 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1393 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1394 errors += 1
1394 errors += 1
1395
1395
1396 return errors != 0
1396 return errors != 0
1397
1397
1398 ## facility to let extension process additional data into an import patch
1398 ## facility to let extension process additional data into an import patch
1399 # list of identifier to be executed in order
1399 # list of identifier to be executed in order
1400 extrapreimport = [] # run before commit
1400 extrapreimport = [] # run before commit
1401 extrapostimport = [] # run after commit
1401 extrapostimport = [] # run after commit
1402 # mapping from identifier to actual import function
1402 # mapping from identifier to actual import function
1403 #
1403 #
1404 # 'preimport' are run before the commit is made and are provided the following
1404 # 'preimport' are run before the commit is made and are provided the following
1405 # arguments:
1405 # arguments:
1406 # - repo: the localrepository instance,
1406 # - repo: the localrepository instance,
1407 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1407 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1408 # - extra: the future extra dictionary of the changeset, please mutate it,
1408 # - extra: the future extra dictionary of the changeset, please mutate it,
1409 # - opts: the import options.
1409 # - opts: the import options.
1410 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1410 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1411 # mutation of in memory commit and more. Feel free to rework the code to get
1411 # mutation of in memory commit and more. Feel free to rework the code to get
1412 # there.
1412 # there.
1413 extrapreimportmap = {}
1413 extrapreimportmap = {}
1414 # 'postimport' are run after the commit is made and are provided the following
1414 # 'postimport' are run after the commit is made and are provided the following
1415 # argument:
1415 # argument:
1416 # - ctx: the changectx created by import.
1416 # - ctx: the changectx created by import.
1417 extrapostimportmap = {}
1417 extrapostimportmap = {}
1418
1418
1419 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1419 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1420 """Utility function used by commands.import to import a single patch
1420 """Utility function used by commands.import to import a single patch
1421
1421
1422 This function is explicitly defined here to help the evolve extension to
1422 This function is explicitly defined here to help the evolve extension to
1423 wrap this part of the import logic.
1423 wrap this part of the import logic.
1424
1424
1425 The API is currently a bit ugly because it a simple code translation from
1425 The API is currently a bit ugly because it a simple code translation from
1426 the import command. Feel free to make it better.
1426 the import command. Feel free to make it better.
1427
1427
1428 :patchdata: a dictionary containing parsed patch data (such as from
1428 :patchdata: a dictionary containing parsed patch data (such as from
1429 ``patch.extract()``)
1429 ``patch.extract()``)
1430 :parents: nodes that will be parent of the created commit
1430 :parents: nodes that will be parent of the created commit
1431 :opts: the full dict of option passed to the import command
1431 :opts: the full dict of option passed to the import command
1432 :msgs: list to save commit message to.
1432 :msgs: list to save commit message to.
1433 (used in case we need to save it when failing)
1433 (used in case we need to save it when failing)
1434 :updatefunc: a function that update a repo to a given node
1434 :updatefunc: a function that update a repo to a given node
1435 updatefunc(<repo>, <node>)
1435 updatefunc(<repo>, <node>)
1436 """
1436 """
1437 # avoid cycle context -> subrepo -> cmdutil
1437 # avoid cycle context -> subrepo -> cmdutil
1438 from . import context
1438 from . import context
1439
1439
1440 tmpname = patchdata.get('filename')
1440 tmpname = patchdata.get('filename')
1441 message = patchdata.get('message')
1441 message = patchdata.get('message')
1442 user = opts.get('user') or patchdata.get('user')
1442 user = opts.get('user') or patchdata.get('user')
1443 date = opts.get('date') or patchdata.get('date')
1443 date = opts.get('date') or patchdata.get('date')
1444 branch = patchdata.get('branch')
1444 branch = patchdata.get('branch')
1445 nodeid = patchdata.get('nodeid')
1445 nodeid = patchdata.get('nodeid')
1446 p1 = patchdata.get('p1')
1446 p1 = patchdata.get('p1')
1447 p2 = patchdata.get('p2')
1447 p2 = patchdata.get('p2')
1448
1448
1449 nocommit = opts.get('no_commit')
1449 nocommit = opts.get('no_commit')
1450 importbranch = opts.get('import_branch')
1450 importbranch = opts.get('import_branch')
1451 update = not opts.get('bypass')
1451 update = not opts.get('bypass')
1452 strip = opts["strip"]
1452 strip = opts["strip"]
1453 prefix = opts["prefix"]
1453 prefix = opts["prefix"]
1454 sim = float(opts.get('similarity') or 0)
1454 sim = float(opts.get('similarity') or 0)
1455
1455
1456 if not tmpname:
1456 if not tmpname:
1457 return None, None, False
1457 return None, None, False
1458
1458
1459 rejects = False
1459 rejects = False
1460
1460
1461 cmdline_message = logmessage(ui, opts)
1461 cmdline_message = logmessage(ui, opts)
1462 if cmdline_message:
1462 if cmdline_message:
1463 # pickup the cmdline msg
1463 # pickup the cmdline msg
1464 message = cmdline_message
1464 message = cmdline_message
1465 elif message:
1465 elif message:
1466 # pickup the patch msg
1466 # pickup the patch msg
1467 message = message.strip()
1467 message = message.strip()
1468 else:
1468 else:
1469 # launch the editor
1469 # launch the editor
1470 message = None
1470 message = None
1471 ui.debug('message:\n%s\n' % (message or ''))
1471 ui.debug('message:\n%s\n' % (message or ''))
1472
1472
1473 if len(parents) == 1:
1473 if len(parents) == 1:
1474 parents.append(repo[nullid])
1474 parents.append(repo[nullid])
1475 if opts.get('exact'):
1475 if opts.get('exact'):
1476 if not nodeid or not p1:
1476 if not nodeid or not p1:
1477 raise error.Abort(_('not a Mercurial patch'))
1477 raise error.Abort(_('not a Mercurial patch'))
1478 p1 = repo[p1]
1478 p1 = repo[p1]
1479 p2 = repo[p2 or nullid]
1479 p2 = repo[p2 or nullid]
1480 elif p2:
1480 elif p2:
1481 try:
1481 try:
1482 p1 = repo[p1]
1482 p1 = repo[p1]
1483 p2 = repo[p2]
1483 p2 = repo[p2]
1484 # Without any options, consider p2 only if the
1484 # Without any options, consider p2 only if the
1485 # patch is being applied on top of the recorded
1485 # patch is being applied on top of the recorded
1486 # first parent.
1486 # first parent.
1487 if p1 != parents[0]:
1487 if p1 != parents[0]:
1488 p1 = parents[0]
1488 p1 = parents[0]
1489 p2 = repo[nullid]
1489 p2 = repo[nullid]
1490 except error.RepoError:
1490 except error.RepoError:
1491 p1, p2 = parents
1491 p1, p2 = parents
1492 if p2.node() == nullid:
1492 if p2.node() == nullid:
1493 ui.warn(_("warning: import the patch as a normal revision\n"
1493 ui.warn(_("warning: import the patch as a normal revision\n"
1494 "(use --exact to import the patch as a merge)\n"))
1494 "(use --exact to import the patch as a merge)\n"))
1495 else:
1495 else:
1496 p1, p2 = parents
1496 p1, p2 = parents
1497
1497
1498 n = None
1498 n = None
1499 if update:
1499 if update:
1500 if p1 != parents[0]:
1500 if p1 != parents[0]:
1501 updatefunc(repo, p1.node())
1501 updatefunc(repo, p1.node())
1502 if p2 != parents[1]:
1502 if p2 != parents[1]:
1503 repo.setparents(p1.node(), p2.node())
1503 repo.setparents(p1.node(), p2.node())
1504
1504
1505 if opts.get('exact') or importbranch:
1505 if opts.get('exact') or importbranch:
1506 repo.dirstate.setbranch(branch or 'default')
1506 repo.dirstate.setbranch(branch or 'default')
1507
1507
1508 partial = opts.get('partial', False)
1508 partial = opts.get('partial', False)
1509 files = set()
1509 files = set()
1510 try:
1510 try:
1511 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1511 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1512 files=files, eolmode=None, similarity=sim / 100.0)
1512 files=files, eolmode=None, similarity=sim / 100.0)
1513 except error.PatchError as e:
1513 except error.PatchError as e:
1514 if not partial:
1514 if not partial:
1515 raise error.Abort(pycompat.bytestr(e))
1515 raise error.Abort(pycompat.bytestr(e))
1516 if partial:
1516 if partial:
1517 rejects = True
1517 rejects = True
1518
1518
1519 files = list(files)
1519 files = list(files)
1520 if nocommit:
1520 if nocommit:
1521 if message:
1521 if message:
1522 msgs.append(message)
1522 msgs.append(message)
1523 else:
1523 else:
1524 if opts.get('exact') or p2:
1524 if opts.get('exact') or p2:
1525 # If you got here, you either use --force and know what
1525 # If you got here, you either use --force and know what
1526 # you are doing or used --exact or a merge patch while
1526 # you are doing or used --exact or a merge patch while
1527 # being updated to its first parent.
1527 # being updated to its first parent.
1528 m = None
1528 m = None
1529 else:
1529 else:
1530 m = scmutil.matchfiles(repo, files or [])
1530 m = scmutil.matchfiles(repo, files or [])
1531 editform = mergeeditform(repo[None], 'import.normal')
1531 editform = mergeeditform(repo[None], 'import.normal')
1532 if opts.get('exact'):
1532 if opts.get('exact'):
1533 editor = None
1533 editor = None
1534 else:
1534 else:
1535 editor = getcommiteditor(editform=editform,
1535 editor = getcommiteditor(editform=editform,
1536 **pycompat.strkwargs(opts))
1536 **pycompat.strkwargs(opts))
1537 extra = {}
1537 extra = {}
1538 for idfunc in extrapreimport:
1538 for idfunc in extrapreimport:
1539 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1539 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1540 overrides = {}
1540 overrides = {}
1541 if partial:
1541 if partial:
1542 overrides[('ui', 'allowemptycommit')] = True
1542 overrides[('ui', 'allowemptycommit')] = True
1543 with repo.ui.configoverride(overrides, 'import'):
1543 with repo.ui.configoverride(overrides, 'import'):
1544 n = repo.commit(message, user,
1544 n = repo.commit(message, user,
1545 date, match=m,
1545 date, match=m,
1546 editor=editor, extra=extra)
1546 editor=editor, extra=extra)
1547 for idfunc in extrapostimport:
1547 for idfunc in extrapostimport:
1548 extrapostimportmap[idfunc](repo[n])
1548 extrapostimportmap[idfunc](repo[n])
1549 else:
1549 else:
1550 if opts.get('exact') or importbranch:
1550 if opts.get('exact') or importbranch:
1551 branch = branch or 'default'
1551 branch = branch or 'default'
1552 else:
1552 else:
1553 branch = p1.branch()
1553 branch = p1.branch()
1554 store = patch.filestore()
1554 store = patch.filestore()
1555 try:
1555 try:
1556 files = set()
1556 files = set()
1557 try:
1557 try:
1558 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1558 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1559 files, eolmode=None)
1559 files, eolmode=None)
1560 except error.PatchError as e:
1560 except error.PatchError as e:
1561 raise error.Abort(stringutil.forcebytestr(e))
1561 raise error.Abort(stringutil.forcebytestr(e))
1562 if opts.get('exact'):
1562 if opts.get('exact'):
1563 editor = None
1563 editor = None
1564 else:
1564 else:
1565 editor = getcommiteditor(editform='import.bypass')
1565 editor = getcommiteditor(editform='import.bypass')
1566 memctx = context.memctx(repo, (p1.node(), p2.node()),
1566 memctx = context.memctx(repo, (p1.node(), p2.node()),
1567 message,
1567 message,
1568 files=files,
1568 files=files,
1569 filectxfn=store,
1569 filectxfn=store,
1570 user=user,
1570 user=user,
1571 date=date,
1571 date=date,
1572 branch=branch,
1572 branch=branch,
1573 editor=editor)
1573 editor=editor)
1574 n = memctx.commit()
1574 n = memctx.commit()
1575 finally:
1575 finally:
1576 store.close()
1576 store.close()
1577 if opts.get('exact') and nocommit:
1577 if opts.get('exact') and nocommit:
1578 # --exact with --no-commit is still useful in that it does merge
1578 # --exact with --no-commit is still useful in that it does merge
1579 # and branch bits
1579 # and branch bits
1580 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1580 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1581 elif opts.get('exact') and (not n or hex(n) != nodeid):
1581 elif opts.get('exact') and (not n or hex(n) != nodeid):
1582 raise error.Abort(_('patch is damaged or loses information'))
1582 raise error.Abort(_('patch is damaged or loses information'))
1583 msg = _('applied to working directory')
1583 msg = _('applied to working directory')
1584 if n:
1584 if n:
1585 # i18n: refers to a short changeset id
1585 # i18n: refers to a short changeset id
1586 msg = _('created %s') % short(n)
1586 msg = _('created %s') % short(n)
1587 return msg, n, rejects
1587 return msg, n, rejects
1588
1588
1589 # facility to let extensions include additional data in an exported patch
1589 # facility to let extensions include additional data in an exported patch
1590 # list of identifiers to be executed in order
1590 # list of identifiers to be executed in order
1591 extraexport = []
1591 extraexport = []
1592 # mapping from identifier to actual export function
1592 # mapping from identifier to actual export function
1593 # function as to return a string to be added to the header or None
1593 # function as to return a string to be added to the header or None
1594 # it is given two arguments (sequencenumber, changectx)
1594 # it is given two arguments (sequencenumber, changectx)
1595 extraexportmap = {}
1595 extraexportmap = {}
1596
1596
1597 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1597 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1598 node = scmutil.binnode(ctx)
1598 node = scmutil.binnode(ctx)
1599 parents = [p.node() for p in ctx.parents() if p]
1599 parents = [p.node() for p in ctx.parents() if p]
1600 branch = ctx.branch()
1600 branch = ctx.branch()
1601 if switch_parent:
1601 if switch_parent:
1602 parents.reverse()
1602 parents.reverse()
1603
1603
1604 if parents:
1604 if parents:
1605 prev = parents[0]
1605 prev = parents[0]
1606 else:
1606 else:
1607 prev = nullid
1607 prev = nullid
1608
1608
1609 fm.context(ctx=ctx)
1609 fm.context(ctx=ctx)
1610 fm.plain('# HG changeset patch\n')
1610 fm.plain('# HG changeset patch\n')
1611 fm.write('user', '# User %s\n', ctx.user())
1611 fm.write('user', '# User %s\n', ctx.user())
1612 fm.plain('# Date %d %d\n' % ctx.date())
1612 fm.plain('# Date %d %d\n' % ctx.date())
1613 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1613 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1614 fm.condwrite(branch and branch != 'default',
1614 fm.condwrite(branch and branch != 'default',
1615 'branch', '# Branch %s\n', branch)
1615 'branch', '# Branch %s\n', branch)
1616 fm.write('node', '# Node ID %s\n', hex(node))
1616 fm.write('node', '# Node ID %s\n', hex(node))
1617 fm.plain('# Parent %s\n' % hex(prev))
1617 fm.plain('# Parent %s\n' % hex(prev))
1618 if len(parents) > 1:
1618 if len(parents) > 1:
1619 fm.plain('# Parent %s\n' % hex(parents[1]))
1619 fm.plain('# Parent %s\n' % hex(parents[1]))
1620 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1620 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1621
1621
1622 # TODO: redesign extraexportmap function to support formatter
1622 # TODO: redesign extraexportmap function to support formatter
1623 for headerid in extraexport:
1623 for headerid in extraexport:
1624 header = extraexportmap[headerid](seqno, ctx)
1624 header = extraexportmap[headerid](seqno, ctx)
1625 if header is not None:
1625 if header is not None:
1626 fm.plain('# %s\n' % header)
1626 fm.plain('# %s\n' % header)
1627
1627
1628 fm.write('desc', '%s\n', ctx.description().rstrip())
1628 fm.write('desc', '%s\n', ctx.description().rstrip())
1629 fm.plain('\n')
1629 fm.plain('\n')
1630
1630
1631 if fm.isplain():
1631 if fm.isplain():
1632 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1632 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1633 for chunk, label in chunkiter:
1633 for chunk, label in chunkiter:
1634 fm.plain(chunk, label=label)
1634 fm.plain(chunk, label=label)
1635 else:
1635 else:
1636 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1636 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1637 # TODO: make it structured?
1637 # TODO: make it structured?
1638 fm.data(diff=b''.join(chunkiter))
1638 fm.data(diff=b''.join(chunkiter))
1639
1639
1640 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1640 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1641 """Export changesets to stdout or a single file"""
1641 """Export changesets to stdout or a single file"""
1642 for seqno, rev in enumerate(revs, 1):
1642 for seqno, rev in enumerate(revs, 1):
1643 ctx = repo[rev]
1643 ctx = repo[rev]
1644 if not dest.startswith('<'):
1644 if not dest.startswith('<'):
1645 repo.ui.note("%s\n" % dest)
1645 repo.ui.note("%s\n" % dest)
1646 fm.startitem()
1646 fm.startitem()
1647 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1647 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1648
1648
1649 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1649 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1650 match):
1650 match):
1651 """Export changesets to possibly multiple files"""
1651 """Export changesets to possibly multiple files"""
1652 total = len(revs)
1652 total = len(revs)
1653 revwidth = max(len(str(rev)) for rev in revs)
1653 revwidth = max(len(str(rev)) for rev in revs)
1654 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1654 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1655
1655
1656 for seqno, rev in enumerate(revs, 1):
1656 for seqno, rev in enumerate(revs, 1):
1657 ctx = repo[rev]
1657 ctx = repo[rev]
1658 dest = makefilename(ctx, fntemplate,
1658 dest = makefilename(ctx, fntemplate,
1659 total=total, seqno=seqno, revwidth=revwidth)
1659 total=total, seqno=seqno, revwidth=revwidth)
1660 filemap.setdefault(dest, []).append((seqno, rev))
1660 filemap.setdefault(dest, []).append((seqno, rev))
1661
1661
1662 for dest in filemap:
1662 for dest in filemap:
1663 with formatter.maybereopen(basefm, dest) as fm:
1663 with formatter.maybereopen(basefm, dest) as fm:
1664 repo.ui.note("%s\n" % dest)
1664 repo.ui.note("%s\n" % dest)
1665 for seqno, rev in filemap[dest]:
1665 for seqno, rev in filemap[dest]:
1666 fm.startitem()
1666 fm.startitem()
1667 ctx = repo[rev]
1667 ctx = repo[rev]
1668 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1668 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1669 diffopts)
1669 diffopts)
1670
1670
1671 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1671 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1672 opts=None, match=None):
1672 opts=None, match=None):
1673 '''export changesets as hg patches
1673 '''export changesets as hg patches
1674
1674
1675 Args:
1675 Args:
1676 repo: The repository from which we're exporting revisions.
1676 repo: The repository from which we're exporting revisions.
1677 revs: A list of revisions to export as revision numbers.
1677 revs: A list of revisions to export as revision numbers.
1678 basefm: A formatter to which patches should be written.
1678 basefm: A formatter to which patches should be written.
1679 fntemplate: An optional string to use for generating patch file names.
1679 fntemplate: An optional string to use for generating patch file names.
1680 switch_parent: If True, show diffs against second parent when not nullid.
1680 switch_parent: If True, show diffs against second parent when not nullid.
1681 Default is false, which always shows diff against p1.
1681 Default is false, which always shows diff against p1.
1682 opts: diff options to use for generating the patch.
1682 opts: diff options to use for generating the patch.
1683 match: If specified, only export changes to files matching this matcher.
1683 match: If specified, only export changes to files matching this matcher.
1684
1684
1685 Returns:
1685 Returns:
1686 Nothing.
1686 Nothing.
1687
1687
1688 Side Effect:
1688 Side Effect:
1689 "HG Changeset Patch" data is emitted to one of the following
1689 "HG Changeset Patch" data is emitted to one of the following
1690 destinations:
1690 destinations:
1691 fntemplate specified: Each rev is written to a unique file named using
1691 fntemplate specified: Each rev is written to a unique file named using
1692 the given template.
1692 the given template.
1693 Otherwise: All revs will be written to basefm.
1693 Otherwise: All revs will be written to basefm.
1694 '''
1694 '''
1695 scmutil.prefetchfiles(repo, revs, match)
1695 scmutil.prefetchfiles(repo, revs, match)
1696
1696
1697 if not fntemplate:
1697 if not fntemplate:
1698 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1698 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1699 else:
1699 else:
1700 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1700 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1701 match)
1701 match)
1702
1702
1703 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1703 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1704 """Export changesets to the given file stream"""
1704 """Export changesets to the given file stream"""
1705 scmutil.prefetchfiles(repo, revs, match)
1705 scmutil.prefetchfiles(repo, revs, match)
1706
1706
1707 dest = getattr(fp, 'name', '<unnamed>')
1707 dest = getattr(fp, 'name', '<unnamed>')
1708 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1708 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1709 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1709 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1710
1710
1711 def showmarker(fm, marker, index=None):
1711 def showmarker(fm, marker, index=None):
1712 """utility function to display obsolescence marker in a readable way
1712 """utility function to display obsolescence marker in a readable way
1713
1713
1714 To be used by debug function."""
1714 To be used by debug function."""
1715 if index is not None:
1715 if index is not None:
1716 fm.write('index', '%i ', index)
1716 fm.write('index', '%i ', index)
1717 fm.write('prednode', '%s ', hex(marker.prednode()))
1717 fm.write('prednode', '%s ', hex(marker.prednode()))
1718 succs = marker.succnodes()
1718 succs = marker.succnodes()
1719 fm.condwrite(succs, 'succnodes', '%s ',
1719 fm.condwrite(succs, 'succnodes', '%s ',
1720 fm.formatlist(map(hex, succs), name='node'))
1720 fm.formatlist(map(hex, succs), name='node'))
1721 fm.write('flag', '%X ', marker.flags())
1721 fm.write('flag', '%X ', marker.flags())
1722 parents = marker.parentnodes()
1722 parents = marker.parentnodes()
1723 if parents is not None:
1723 if parents is not None:
1724 fm.write('parentnodes', '{%s} ',
1724 fm.write('parentnodes', '{%s} ',
1725 fm.formatlist(map(hex, parents), name='node', sep=', '))
1725 fm.formatlist(map(hex, parents), name='node', sep=', '))
1726 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1726 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1727 meta = marker.metadata().copy()
1727 meta = marker.metadata().copy()
1728 meta.pop('date', None)
1728 meta.pop('date', None)
1729 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1729 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1730 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1730 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1731 fm.plain('\n')
1731 fm.plain('\n')
1732
1732
1733 def finddate(ui, repo, date):
1733 def finddate(ui, repo, date):
1734 """Find the tipmost changeset that matches the given date spec"""
1734 """Find the tipmost changeset that matches the given date spec"""
1735
1735
1736 df = dateutil.matchdate(date)
1736 df = dateutil.matchdate(date)
1737 m = scmutil.matchall(repo)
1737 m = scmutil.matchall(repo)
1738 results = {}
1738 results = {}
1739
1739
1740 def prep(ctx, fns):
1740 def prep(ctx, fns):
1741 d = ctx.date()
1741 d = ctx.date()
1742 if df(d[0]):
1742 if df(d[0]):
1743 results[ctx.rev()] = d
1743 results[ctx.rev()] = d
1744
1744
1745 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1745 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1746 rev = ctx.rev()
1746 rev = ctx.rev()
1747 if rev in results:
1747 if rev in results:
1748 ui.status(_("found revision %s from %s\n") %
1748 ui.status(_("found revision %s from %s\n") %
1749 (rev, dateutil.datestr(results[rev])))
1749 (rev, dateutil.datestr(results[rev])))
1750 return '%d' % rev
1750 return '%d' % rev
1751
1751
1752 raise error.Abort(_("revision matching date not found"))
1752 raise error.Abort(_("revision matching date not found"))
1753
1753
1754 def increasingwindows(windowsize=8, sizelimit=512):
1754 def increasingwindows(windowsize=8, sizelimit=512):
1755 while True:
1755 while True:
1756 yield windowsize
1756 yield windowsize
1757 if windowsize < sizelimit:
1757 if windowsize < sizelimit:
1758 windowsize *= 2
1758 windowsize *= 2
1759
1759
1760 def _walkrevs(repo, opts):
1760 def _walkrevs(repo, opts):
1761 # Default --rev value depends on --follow but --follow behavior
1761 # Default --rev value depends on --follow but --follow behavior
1762 # depends on revisions resolved from --rev...
1762 # depends on revisions resolved from --rev...
1763 follow = opts.get('follow') or opts.get('follow_first')
1763 follow = opts.get('follow') or opts.get('follow_first')
1764 if opts.get('rev'):
1764 if opts.get('rev'):
1765 revs = scmutil.revrange(repo, opts['rev'])
1765 revs = scmutil.revrange(repo, opts['rev'])
1766 elif follow and repo.dirstate.p1() == nullid:
1766 elif follow and repo.dirstate.p1() == nullid:
1767 revs = smartset.baseset()
1767 revs = smartset.baseset()
1768 elif follow:
1768 elif follow:
1769 revs = repo.revs('reverse(:.)')
1769 revs = repo.revs('reverse(:.)')
1770 else:
1770 else:
1771 revs = smartset.spanset(repo)
1771 revs = smartset.spanset(repo)
1772 revs.reverse()
1772 revs.reverse()
1773 return revs
1773 return revs
1774
1774
1775 class FileWalkError(Exception):
1775 class FileWalkError(Exception):
1776 pass
1776 pass
1777
1777
1778 def walkfilerevs(repo, match, follow, revs, fncache):
1778 def walkfilerevs(repo, match, follow, revs, fncache):
1779 '''Walks the file history for the matched files.
1779 '''Walks the file history for the matched files.
1780
1780
1781 Returns the changeset revs that are involved in the file history.
1781 Returns the changeset revs that are involved in the file history.
1782
1782
1783 Throws FileWalkError if the file history can't be walked using
1783 Throws FileWalkError if the file history can't be walked using
1784 filelogs alone.
1784 filelogs alone.
1785 '''
1785 '''
1786 wanted = set()
1786 wanted = set()
1787 copies = []
1787 copies = []
1788 minrev, maxrev = min(revs), max(revs)
1788 minrev, maxrev = min(revs), max(revs)
1789 def filerevs(filelog, last):
1789 def filerevs(filelog, last):
1790 """
1790 """
1791 Only files, no patterns. Check the history of each file.
1791 Only files, no patterns. Check the history of each file.
1792
1792
1793 Examines filelog entries within minrev, maxrev linkrev range
1793 Examines filelog entries within minrev, maxrev linkrev range
1794 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1794 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1795 tuples in backwards order
1795 tuples in backwards order
1796 """
1796 """
1797 cl_count = len(repo)
1797 cl_count = len(repo)
1798 revs = []
1798 revs = []
1799 for j in pycompat.xrange(0, last + 1):
1799 for j in pycompat.xrange(0, last + 1):
1800 linkrev = filelog.linkrev(j)
1800 linkrev = filelog.linkrev(j)
1801 if linkrev < minrev:
1801 if linkrev < minrev:
1802 continue
1802 continue
1803 # only yield rev for which we have the changelog, it can
1803 # only yield rev for which we have the changelog, it can
1804 # happen while doing "hg log" during a pull or commit
1804 # happen while doing "hg log" during a pull or commit
1805 if linkrev >= cl_count:
1805 if linkrev >= cl_count:
1806 break
1806 break
1807
1807
1808 parentlinkrevs = []
1808 parentlinkrevs = []
1809 for p in filelog.parentrevs(j):
1809 for p in filelog.parentrevs(j):
1810 if p != nullrev:
1810 if p != nullrev:
1811 parentlinkrevs.append(filelog.linkrev(p))
1811 parentlinkrevs.append(filelog.linkrev(p))
1812 n = filelog.node(j)
1812 n = filelog.node(j)
1813 revs.append((linkrev, parentlinkrevs,
1813 revs.append((linkrev, parentlinkrevs,
1814 follow and filelog.renamed(n)))
1814 follow and filelog.renamed(n)))
1815
1815
1816 return reversed(revs)
1816 return reversed(revs)
1817 def iterfiles():
1817 def iterfiles():
1818 pctx = repo['.']
1818 pctx = repo['.']
1819 for filename in match.files():
1819 for filename in match.files():
1820 if follow:
1820 if follow:
1821 if filename not in pctx:
1821 if filename not in pctx:
1822 raise error.Abort(_('cannot follow file not in parent '
1822 raise error.Abort(_('cannot follow file not in parent '
1823 'revision: "%s"') % filename)
1823 'revision: "%s"') % filename)
1824 yield filename, pctx[filename].filenode()
1824 yield filename, pctx[filename].filenode()
1825 else:
1825 else:
1826 yield filename, None
1826 yield filename, None
1827 for filename_node in copies:
1827 for filename_node in copies:
1828 yield filename_node
1828 yield filename_node
1829
1829
1830 for file_, node in iterfiles():
1830 for file_, node in iterfiles():
1831 filelog = repo.file(file_)
1831 filelog = repo.file(file_)
1832 if not len(filelog):
1832 if not len(filelog):
1833 if node is None:
1833 if node is None:
1834 # A zero count may be a directory or deleted file, so
1834 # A zero count may be a directory or deleted file, so
1835 # try to find matching entries on the slow path.
1835 # try to find matching entries on the slow path.
1836 if follow:
1836 if follow:
1837 raise error.Abort(
1837 raise error.Abort(
1838 _('cannot follow nonexistent file: "%s"') % file_)
1838 _('cannot follow nonexistent file: "%s"') % file_)
1839 raise FileWalkError("Cannot walk via filelog")
1839 raise FileWalkError("Cannot walk via filelog")
1840 else:
1840 else:
1841 continue
1841 continue
1842
1842
1843 if node is None:
1843 if node is None:
1844 last = len(filelog) - 1
1844 last = len(filelog) - 1
1845 else:
1845 else:
1846 last = filelog.rev(node)
1846 last = filelog.rev(node)
1847
1847
1848 # keep track of all ancestors of the file
1848 # keep track of all ancestors of the file
1849 ancestors = {filelog.linkrev(last)}
1849 ancestors = {filelog.linkrev(last)}
1850
1850
1851 # iterate from latest to oldest revision
1851 # iterate from latest to oldest revision
1852 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1852 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1853 if not follow:
1853 if not follow:
1854 if rev > maxrev:
1854 if rev > maxrev:
1855 continue
1855 continue
1856 else:
1856 else:
1857 # Note that last might not be the first interesting
1857 # Note that last might not be the first interesting
1858 # rev to us:
1858 # rev to us:
1859 # if the file has been changed after maxrev, we'll
1859 # if the file has been changed after maxrev, we'll
1860 # have linkrev(last) > maxrev, and we still need
1860 # have linkrev(last) > maxrev, and we still need
1861 # to explore the file graph
1861 # to explore the file graph
1862 if rev not in ancestors:
1862 if rev not in ancestors:
1863 continue
1863 continue
1864 # XXX insert 1327 fix here
1864 # XXX insert 1327 fix here
1865 if flparentlinkrevs:
1865 if flparentlinkrevs:
1866 ancestors.update(flparentlinkrevs)
1866 ancestors.update(flparentlinkrevs)
1867
1867
1868 fncache.setdefault(rev, []).append(file_)
1868 fncache.setdefault(rev, []).append(file_)
1869 wanted.add(rev)
1869 wanted.add(rev)
1870 if copied:
1870 if copied:
1871 copies.append(copied)
1871 copies.append(copied)
1872
1872
1873 return wanted
1873 return wanted
1874
1874
1875 class _followfilter(object):
1875 class _followfilter(object):
1876 def __init__(self, repo, onlyfirst=False):
1876 def __init__(self, repo, onlyfirst=False):
1877 self.repo = repo
1877 self.repo = repo
1878 self.startrev = nullrev
1878 self.startrev = nullrev
1879 self.roots = set()
1879 self.roots = set()
1880 self.onlyfirst = onlyfirst
1880 self.onlyfirst = onlyfirst
1881
1881
1882 def match(self, rev):
1882 def match(self, rev):
1883 def realparents(rev):
1883 def realparents(rev):
1884 if self.onlyfirst:
1884 if self.onlyfirst:
1885 return self.repo.changelog.parentrevs(rev)[0:1]
1885 return self.repo.changelog.parentrevs(rev)[0:1]
1886 else:
1886 else:
1887 return filter(lambda x: x != nullrev,
1887 return filter(lambda x: x != nullrev,
1888 self.repo.changelog.parentrevs(rev))
1888 self.repo.changelog.parentrevs(rev))
1889
1889
1890 if self.startrev == nullrev:
1890 if self.startrev == nullrev:
1891 self.startrev = rev
1891 self.startrev = rev
1892 return True
1892 return True
1893
1893
1894 if rev > self.startrev:
1894 if rev > self.startrev:
1895 # forward: all descendants
1895 # forward: all descendants
1896 if not self.roots:
1896 if not self.roots:
1897 self.roots.add(self.startrev)
1897 self.roots.add(self.startrev)
1898 for parent in realparents(rev):
1898 for parent in realparents(rev):
1899 if parent in self.roots:
1899 if parent in self.roots:
1900 self.roots.add(rev)
1900 self.roots.add(rev)
1901 return True
1901 return True
1902 else:
1902 else:
1903 # backwards: all parents
1903 # backwards: all parents
1904 if not self.roots:
1904 if not self.roots:
1905 self.roots.update(realparents(self.startrev))
1905 self.roots.update(realparents(self.startrev))
1906 if rev in self.roots:
1906 if rev in self.roots:
1907 self.roots.remove(rev)
1907 self.roots.remove(rev)
1908 self.roots.update(realparents(rev))
1908 self.roots.update(realparents(rev))
1909 return True
1909 return True
1910
1910
1911 return False
1911 return False
1912
1912
1913 def walkchangerevs(repo, match, opts, prepare):
1913 def walkchangerevs(repo, match, opts, prepare):
1914 '''Iterate over files and the revs in which they changed.
1914 '''Iterate over files and the revs in which they changed.
1915
1915
1916 Callers most commonly need to iterate backwards over the history
1916 Callers most commonly need to iterate backwards over the history
1917 in which they are interested. Doing so has awful (quadratic-looking)
1917 in which they are interested. Doing so has awful (quadratic-looking)
1918 performance, so we use iterators in a "windowed" way.
1918 performance, so we use iterators in a "windowed" way.
1919
1919
1920 We walk a window of revisions in the desired order. Within the
1920 We walk a window of revisions in the desired order. Within the
1921 window, we first walk forwards to gather data, then in the desired
1921 window, we first walk forwards to gather data, then in the desired
1922 order (usually backwards) to display it.
1922 order (usually backwards) to display it.
1923
1923
1924 This function returns an iterator yielding contexts. Before
1924 This function returns an iterator yielding contexts. Before
1925 yielding each context, the iterator will first call the prepare
1925 yielding each context, the iterator will first call the prepare
1926 function on each context in the window in forward order.'''
1926 function on each context in the window in forward order.'''
1927
1927
1928 allfiles = opts.get('all_files')
1928 allfiles = opts.get('all_files')
1929 follow = opts.get('follow') or opts.get('follow_first')
1929 follow = opts.get('follow') or opts.get('follow_first')
1930 revs = _walkrevs(repo, opts)
1930 revs = _walkrevs(repo, opts)
1931 if not revs:
1931 if not revs:
1932 return []
1932 return []
1933 wanted = set()
1933 wanted = set()
1934 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1934 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1935 fncache = {}
1935 fncache = {}
1936 change = repo.__getitem__
1936 change = repo.__getitem__
1937
1937
1938 # First step is to fill wanted, the set of revisions that we want to yield.
1938 # First step is to fill wanted, the set of revisions that we want to yield.
1939 # When it does not induce extra cost, we also fill fncache for revisions in
1939 # When it does not induce extra cost, we also fill fncache for revisions in
1940 # wanted: a cache of filenames that were changed (ctx.files()) and that
1940 # wanted: a cache of filenames that were changed (ctx.files()) and that
1941 # match the file filtering conditions.
1941 # match the file filtering conditions.
1942
1942
1943 if match.always() or allfiles:
1943 if match.always() or allfiles:
1944 # No files, no patterns. Display all revs.
1944 # No files, no patterns. Display all revs.
1945 wanted = revs
1945 wanted = revs
1946 elif not slowpath:
1946 elif not slowpath:
1947 # We only have to read through the filelog to find wanted revisions
1947 # We only have to read through the filelog to find wanted revisions
1948
1948
1949 try:
1949 try:
1950 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1950 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1951 except FileWalkError:
1951 except FileWalkError:
1952 slowpath = True
1952 slowpath = True
1953
1953
1954 # We decided to fall back to the slowpath because at least one
1954 # We decided to fall back to the slowpath because at least one
1955 # of the paths was not a file. Check to see if at least one of them
1955 # of the paths was not a file. Check to see if at least one of them
1956 # existed in history, otherwise simply return
1956 # existed in history, otherwise simply return
1957 for path in match.files():
1957 for path in match.files():
1958 if path == '.' or path in repo.store:
1958 if path == '.' or path in repo.store:
1959 break
1959 break
1960 else:
1960 else:
1961 return []
1961 return []
1962
1962
1963 if slowpath:
1963 if slowpath:
1964 # We have to read the changelog to match filenames against
1964 # We have to read the changelog to match filenames against
1965 # changed files
1965 # changed files
1966
1966
1967 if follow:
1967 if follow:
1968 raise error.Abort(_('can only follow copies/renames for explicit '
1968 raise error.Abort(_('can only follow copies/renames for explicit '
1969 'filenames'))
1969 'filenames'))
1970
1970
1971 # The slow path checks files modified in every changeset.
1971 # The slow path checks files modified in every changeset.
1972 # This is really slow on large repos, so compute the set lazily.
1972 # This is really slow on large repos, so compute the set lazily.
1973 class lazywantedset(object):
1973 class lazywantedset(object):
1974 def __init__(self):
1974 def __init__(self):
1975 self.set = set()
1975 self.set = set()
1976 self.revs = set(revs)
1976 self.revs = set(revs)
1977
1977
1978 # No need to worry about locality here because it will be accessed
1978 # No need to worry about locality here because it will be accessed
1979 # in the same order as the increasing window below.
1979 # in the same order as the increasing window below.
1980 def __contains__(self, value):
1980 def __contains__(self, value):
1981 if value in self.set:
1981 if value in self.set:
1982 return True
1982 return True
1983 elif not value in self.revs:
1983 elif not value in self.revs:
1984 return False
1984 return False
1985 else:
1985 else:
1986 self.revs.discard(value)
1986 self.revs.discard(value)
1987 ctx = change(value)
1987 ctx = change(value)
1988 if allfiles:
1988 if allfiles:
1989 matches = list(ctx.manifest().walk(match))
1989 matches = list(ctx.manifest().walk(match))
1990 else:
1990 else:
1991 matches = [f for f in ctx.files() if match(f)]
1991 matches = [f for f in ctx.files() if match(f)]
1992 if matches:
1992 if matches:
1993 fncache[value] = matches
1993 fncache[value] = matches
1994 self.set.add(value)
1994 self.set.add(value)
1995 return True
1995 return True
1996 return False
1996 return False
1997
1997
1998 def discard(self, value):
1998 def discard(self, value):
1999 self.revs.discard(value)
1999 self.revs.discard(value)
2000 self.set.discard(value)
2000 self.set.discard(value)
2001
2001
2002 wanted = lazywantedset()
2002 wanted = lazywantedset()
2003
2003
2004 # it might be worthwhile to do this in the iterator if the rev range
2004 # it might be worthwhile to do this in the iterator if the rev range
2005 # is descending and the prune args are all within that range
2005 # is descending and the prune args are all within that range
2006 for rev in opts.get('prune', ()):
2006 for rev in opts.get('prune', ()):
2007 rev = repo[rev].rev()
2007 rev = repo[rev].rev()
2008 ff = _followfilter(repo)
2008 ff = _followfilter(repo)
2009 stop = min(revs[0], revs[-1])
2009 stop = min(revs[0], revs[-1])
2010 for x in pycompat.xrange(rev, stop - 1, -1):
2010 for x in pycompat.xrange(rev, stop - 1, -1):
2011 if ff.match(x):
2011 if ff.match(x):
2012 wanted = wanted - [x]
2012 wanted = wanted - [x]
2013
2013
2014 # Now that wanted is correctly initialized, we can iterate over the
2014 # Now that wanted is correctly initialized, we can iterate over the
2015 # revision range, yielding only revisions in wanted.
2015 # revision range, yielding only revisions in wanted.
2016 def iterate():
2016 def iterate():
2017 if follow and match.always():
2017 if follow and match.always():
2018 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2018 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2019 def want(rev):
2019 def want(rev):
2020 return ff.match(rev) and rev in wanted
2020 return ff.match(rev) and rev in wanted
2021 else:
2021 else:
2022 def want(rev):
2022 def want(rev):
2023 return rev in wanted
2023 return rev in wanted
2024
2024
2025 it = iter(revs)
2025 it = iter(revs)
2026 stopiteration = False
2026 stopiteration = False
2027 for windowsize in increasingwindows():
2027 for windowsize in increasingwindows():
2028 nrevs = []
2028 nrevs = []
2029 for i in pycompat.xrange(windowsize):
2029 for i in pycompat.xrange(windowsize):
2030 rev = next(it, None)
2030 rev = next(it, None)
2031 if rev is None:
2031 if rev is None:
2032 stopiteration = True
2032 stopiteration = True
2033 break
2033 break
2034 elif want(rev):
2034 elif want(rev):
2035 nrevs.append(rev)
2035 nrevs.append(rev)
2036 for rev in sorted(nrevs):
2036 for rev in sorted(nrevs):
2037 fns = fncache.get(rev)
2037 fns = fncache.get(rev)
2038 ctx = change(rev)
2038 ctx = change(rev)
2039 if not fns:
2039 if not fns:
2040 def fns_generator():
2040 def fns_generator():
2041 if allfiles:
2041 if allfiles:
2042 fiter = iter(ctx)
2042 fiter = iter(ctx)
2043 else:
2043 else:
2044 fiter = ctx.files()
2044 fiter = ctx.files()
2045 for f in fiter:
2045 for f in fiter:
2046 if match(f):
2046 if match(f):
2047 yield f
2047 yield f
2048 fns = fns_generator()
2048 fns = fns_generator()
2049 prepare(ctx, fns)
2049 prepare(ctx, fns)
2050 for rev in nrevs:
2050 for rev in nrevs:
2051 yield change(rev)
2051 yield change(rev)
2052
2052
2053 if stopiteration:
2053 if stopiteration:
2054 break
2054 break
2055
2055
2056 return iterate()
2056 return iterate()
2057
2057
2058 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2058 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2059 bad = []
2059 bad = []
2060
2060
2061 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2061 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2062 names = []
2062 names = []
2063 wctx = repo[None]
2063 wctx = repo[None]
2064 cca = None
2064 cca = None
2065 abort, warn = scmutil.checkportabilityalert(ui)
2065 abort, warn = scmutil.checkportabilityalert(ui)
2066 if abort or warn:
2066 if abort or warn:
2067 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2067 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2068
2068
2069 match = repo.narrowmatch(match, includeexact=True)
2069 match = repo.narrowmatch(match, includeexact=True)
2070 badmatch = matchmod.badmatch(match, badfn)
2070 badmatch = matchmod.badmatch(match, badfn)
2071 dirstate = repo.dirstate
2071 dirstate = repo.dirstate
2072 # We don't want to just call wctx.walk here, since it would return a lot of
2072 # We don't want to just call wctx.walk here, since it would return a lot of
2073 # clean files, which we aren't interested in and takes time.
2073 # clean files, which we aren't interested in and takes time.
2074 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2074 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2075 unknown=True, ignored=False, full=False)):
2075 unknown=True, ignored=False, full=False)):
2076 exact = match.exact(f)
2076 exact = match.exact(f)
2077 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2077 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2078 if cca:
2078 if cca:
2079 cca(f)
2079 cca(f)
2080 names.append(f)
2080 names.append(f)
2081 if ui.verbose or not exact:
2081 if ui.verbose or not exact:
2082 ui.status(_('adding %s\n') % uipathfn(f),
2082 ui.status(_('adding %s\n') % uipathfn(f),
2083 label='ui.addremove.added')
2083 label='ui.addremove.added')
2084
2084
2085 for subpath in sorted(wctx.substate):
2085 for subpath in sorted(wctx.substate):
2086 sub = wctx.sub(subpath)
2086 sub = wctx.sub(subpath)
2087 try:
2087 try:
2088 submatch = matchmod.subdirmatcher(subpath, match)
2088 submatch = matchmod.subdirmatcher(subpath, match)
2089 subprefix = repo.wvfs.reljoin(prefix, subpath)
2089 subprefix = repo.wvfs.reljoin(prefix, subpath)
2090 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2090 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2091 if opts.get(r'subrepos'):
2091 if opts.get(r'subrepos'):
2092 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2092 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2093 **opts))
2093 **opts))
2094 else:
2094 else:
2095 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2095 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2096 **opts))
2096 **opts))
2097 except error.LookupError:
2097 except error.LookupError:
2098 ui.status(_("skipping missing subrepository: %s\n")
2098 ui.status(_("skipping missing subrepository: %s\n")
2099 % uipathfn(subpath))
2099 % uipathfn(subpath))
2100
2100
2101 if not opts.get(r'dry_run'):
2101 if not opts.get(r'dry_run'):
2102 rejected = wctx.add(names, prefix)
2102 rejected = wctx.add(names, prefix)
2103 bad.extend(f for f in rejected if f in match.files())
2103 bad.extend(f for f in rejected if f in match.files())
2104 return bad
2104 return bad
2105
2105
2106 def addwebdirpath(repo, serverpath, webconf):
2106 def addwebdirpath(repo, serverpath, webconf):
2107 webconf[serverpath] = repo.root
2107 webconf[serverpath] = repo.root
2108 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2108 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2109
2109
2110 for r in repo.revs('filelog("path:.hgsub")'):
2110 for r in repo.revs('filelog("path:.hgsub")'):
2111 ctx = repo[r]
2111 ctx = repo[r]
2112 for subpath in ctx.substate:
2112 for subpath in ctx.substate:
2113 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2113 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2114
2114
2115 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2115 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2116 interactive):
2116 interactive):
2117 if dryrun and interactive:
2117 if dryrun and interactive:
2118 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2118 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2119 bad = []
2119 bad = []
2120 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2120 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2121 wctx = repo[None]
2121 wctx = repo[None]
2122 forgot = []
2122 forgot = []
2123
2123
2124 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2124 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2125 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2125 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2126 if explicitonly:
2126 if explicitonly:
2127 forget = [f for f in forget if match.exact(f)]
2127 forget = [f for f in forget if match.exact(f)]
2128
2128
2129 for subpath in sorted(wctx.substate):
2129 for subpath in sorted(wctx.substate):
2130 sub = wctx.sub(subpath)
2130 sub = wctx.sub(subpath)
2131 submatch = matchmod.subdirmatcher(subpath, match)
2131 submatch = matchmod.subdirmatcher(subpath, match)
2132 subprefix = repo.wvfs.reljoin(prefix, subpath)
2132 subprefix = repo.wvfs.reljoin(prefix, subpath)
2133 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2133 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2134 try:
2134 try:
2135 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2135 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2136 dryrun=dryrun,
2136 dryrun=dryrun,
2137 interactive=interactive)
2137 interactive=interactive)
2138 bad.extend([subpath + '/' + f for f in subbad])
2138 bad.extend([subpath + '/' + f for f in subbad])
2139 forgot.extend([subpath + '/' + f for f in subforgot])
2139 forgot.extend([subpath + '/' + f for f in subforgot])
2140 except error.LookupError:
2140 except error.LookupError:
2141 ui.status(_("skipping missing subrepository: %s\n")
2141 ui.status(_("skipping missing subrepository: %s\n")
2142 % uipathfn(subpath))
2142 % uipathfn(subpath))
2143
2143
2144 if not explicitonly:
2144 if not explicitonly:
2145 for f in match.files():
2145 for f in match.files():
2146 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2146 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2147 if f not in forgot:
2147 if f not in forgot:
2148 if repo.wvfs.exists(f):
2148 if repo.wvfs.exists(f):
2149 # Don't complain if the exact case match wasn't given.
2149 # Don't complain if the exact case match wasn't given.
2150 # But don't do this until after checking 'forgot', so
2150 # But don't do this until after checking 'forgot', so
2151 # that subrepo files aren't normalized, and this op is
2151 # that subrepo files aren't normalized, and this op is
2152 # purely from data cached by the status walk above.
2152 # purely from data cached by the status walk above.
2153 if repo.dirstate.normalize(f) in repo.dirstate:
2153 if repo.dirstate.normalize(f) in repo.dirstate:
2154 continue
2154 continue
2155 ui.warn(_('not removing %s: '
2155 ui.warn(_('not removing %s: '
2156 'file is already untracked\n')
2156 'file is already untracked\n')
2157 % uipathfn(f))
2157 % uipathfn(f))
2158 bad.append(f)
2158 bad.append(f)
2159
2159
2160 if interactive:
2160 if interactive:
2161 responses = _('[Ynsa?]'
2161 responses = _('[Ynsa?]'
2162 '$$ &Yes, forget this file'
2162 '$$ &Yes, forget this file'
2163 '$$ &No, skip this file'
2163 '$$ &No, skip this file'
2164 '$$ &Skip remaining files'
2164 '$$ &Skip remaining files'
2165 '$$ Include &all remaining files'
2165 '$$ Include &all remaining files'
2166 '$$ &? (display help)')
2166 '$$ &? (display help)')
2167 for filename in forget[:]:
2167 for filename in forget[:]:
2168 r = ui.promptchoice(_('forget %s %s') %
2168 r = ui.promptchoice(_('forget %s %s') %
2169 (uipathfn(filename), responses))
2169 (uipathfn(filename), responses))
2170 if r == 4: # ?
2170 if r == 4: # ?
2171 while r == 4:
2171 while r == 4:
2172 for c, t in ui.extractchoices(responses)[1]:
2172 for c, t in ui.extractchoices(responses)[1]:
2173 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2173 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2174 r = ui.promptchoice(_('forget %s %s') %
2174 r = ui.promptchoice(_('forget %s %s') %
2175 (uipathfn(filename), responses))
2175 (uipathfn(filename), responses))
2176 if r == 0: # yes
2176 if r == 0: # yes
2177 continue
2177 continue
2178 elif r == 1: # no
2178 elif r == 1: # no
2179 forget.remove(filename)
2179 forget.remove(filename)
2180 elif r == 2: # Skip
2180 elif r == 2: # Skip
2181 fnindex = forget.index(filename)
2181 fnindex = forget.index(filename)
2182 del forget[fnindex:]
2182 del forget[fnindex:]
2183 break
2183 break
2184 elif r == 3: # All
2184 elif r == 3: # All
2185 break
2185 break
2186
2186
2187 for f in forget:
2187 for f in forget:
2188 if ui.verbose or not match.exact(f) or interactive:
2188 if ui.verbose or not match.exact(f) or interactive:
2189 ui.status(_('removing %s\n') % uipathfn(f),
2189 ui.status(_('removing %s\n') % uipathfn(f),
2190 label='ui.addremove.removed')
2190 label='ui.addremove.removed')
2191
2191
2192 if not dryrun:
2192 if not dryrun:
2193 rejected = wctx.forget(forget, prefix)
2193 rejected = wctx.forget(forget, prefix)
2194 bad.extend(f for f in rejected if f in match.files())
2194 bad.extend(f for f in rejected if f in match.files())
2195 forgot.extend(f for f in forget if f not in rejected)
2195 forgot.extend(f for f in forget if f not in rejected)
2196 return bad, forgot
2196 return bad, forgot
2197
2197
2198 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2198 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2199 ret = 1
2199 ret = 1
2200
2200
2201 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2201 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2202 for f in ctx.matches(m):
2202 for f in ctx.matches(m):
2203 fm.startitem()
2203 fm.startitem()
2204 fm.context(ctx=ctx)
2204 fm.context(ctx=ctx)
2205 if needsfctx:
2205 if needsfctx:
2206 fc = ctx[f]
2206 fc = ctx[f]
2207 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2207 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2208 fm.data(path=f)
2208 fm.data(path=f)
2209 fm.plain(fmt % uipathfn(f))
2209 fm.plain(fmt % uipathfn(f))
2210 ret = 0
2210 ret = 0
2211
2211
2212 for subpath in sorted(ctx.substate):
2212 for subpath in sorted(ctx.substate):
2213 submatch = matchmod.subdirmatcher(subpath, m)
2213 submatch = matchmod.subdirmatcher(subpath, m)
2214 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2214 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2215 if (subrepos or m.exact(subpath) or any(submatch.files())):
2215 if (subrepos or m.exact(subpath) or any(submatch.files())):
2216 sub = ctx.sub(subpath)
2216 sub = ctx.sub(subpath)
2217 try:
2217 try:
2218 recurse = m.exact(subpath) or subrepos
2218 recurse = m.exact(subpath) or subrepos
2219 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2219 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2220 recurse) == 0:
2220 recurse) == 0:
2221 ret = 0
2221 ret = 0
2222 except error.LookupError:
2222 except error.LookupError:
2223 ui.status(_("skipping missing subrepository: %s\n")
2223 ui.status(_("skipping missing subrepository: %s\n")
2224 % uipathfn(subpath))
2224 % uipathfn(subpath))
2225
2225
2226 return ret
2226 return ret
2227
2227
2228 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2228 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2229 warnings=None):
2229 warnings=None):
2230 ret = 0
2230 ret = 0
2231 s = repo.status(match=m, clean=True)
2231 s = repo.status(match=m, clean=True)
2232 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2232 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2233
2233
2234 wctx = repo[None]
2234 wctx = repo[None]
2235
2235
2236 if warnings is None:
2236 if warnings is None:
2237 warnings = []
2237 warnings = []
2238 warn = True
2238 warn = True
2239 else:
2239 else:
2240 warn = False
2240 warn = False
2241
2241
2242 subs = sorted(wctx.substate)
2242 subs = sorted(wctx.substate)
2243 progress = ui.makeprogress(_('searching'), total=len(subs),
2243 progress = ui.makeprogress(_('searching'), total=len(subs),
2244 unit=_('subrepos'))
2244 unit=_('subrepos'))
2245 for subpath in subs:
2245 for subpath in subs:
2246 submatch = matchmod.subdirmatcher(subpath, m)
2246 submatch = matchmod.subdirmatcher(subpath, m)
2247 subprefix = repo.wvfs.reljoin(prefix, subpath)
2247 subprefix = repo.wvfs.reljoin(prefix, subpath)
2248 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2248 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2249 if subrepos or m.exact(subpath) or any(submatch.files()):
2249 if subrepos or m.exact(subpath) or any(submatch.files()):
2250 progress.increment()
2250 progress.increment()
2251 sub = wctx.sub(subpath)
2251 sub = wctx.sub(subpath)
2252 try:
2252 try:
2253 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2253 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2254 force, subrepos, dryrun, warnings):
2254 force, subrepos, dryrun, warnings):
2255 ret = 1
2255 ret = 1
2256 except error.LookupError:
2256 except error.LookupError:
2257 warnings.append(_("skipping missing subrepository: %s\n")
2257 warnings.append(_("skipping missing subrepository: %s\n")
2258 % uipathfn(subpath))
2258 % uipathfn(subpath))
2259 progress.complete()
2259 progress.complete()
2260
2260
2261 # warn about failure to delete explicit files/dirs
2261 # warn about failure to delete explicit files/dirs
2262 deleteddirs = util.dirs(deleted)
2262 deleteddirs = util.dirs(deleted)
2263 files = m.files()
2263 files = m.files()
2264 progress = ui.makeprogress(_('deleting'), total=len(files),
2264 progress = ui.makeprogress(_('deleting'), total=len(files),
2265 unit=_('files'))
2265 unit=_('files'))
2266 for f in files:
2266 for f in files:
2267 def insubrepo():
2267 def insubrepo():
2268 for subpath in wctx.substate:
2268 for subpath in wctx.substate:
2269 if f.startswith(subpath + '/'):
2269 if f.startswith(subpath + '/'):
2270 return True
2270 return True
2271 return False
2271 return False
2272
2272
2273 progress.increment()
2273 progress.increment()
2274 isdir = f in deleteddirs or wctx.hasdir(f)
2274 isdir = f in deleteddirs or wctx.hasdir(f)
2275 if (f in repo.dirstate or isdir or f == '.'
2275 if (f in repo.dirstate or isdir or f == '.'
2276 or insubrepo() or f in subs):
2276 or insubrepo() or f in subs):
2277 continue
2277 continue
2278
2278
2279 if repo.wvfs.exists(f):
2279 if repo.wvfs.exists(f):
2280 if repo.wvfs.isdir(f):
2280 if repo.wvfs.isdir(f):
2281 warnings.append(_('not removing %s: no tracked files\n')
2281 warnings.append(_('not removing %s: no tracked files\n')
2282 % uipathfn(f))
2282 % uipathfn(f))
2283 else:
2283 else:
2284 warnings.append(_('not removing %s: file is untracked\n')
2284 warnings.append(_('not removing %s: file is untracked\n')
2285 % uipathfn(f))
2285 % uipathfn(f))
2286 # missing files will generate a warning elsewhere
2286 # missing files will generate a warning elsewhere
2287 ret = 1
2287 ret = 1
2288 progress.complete()
2288 progress.complete()
2289
2289
2290 if force:
2290 if force:
2291 list = modified + deleted + clean + added
2291 list = modified + deleted + clean + added
2292 elif after:
2292 elif after:
2293 list = deleted
2293 list = deleted
2294 remaining = modified + added + clean
2294 remaining = modified + added + clean
2295 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2295 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2296 unit=_('files'))
2296 unit=_('files'))
2297 for f in remaining:
2297 for f in remaining:
2298 progress.increment()
2298 progress.increment()
2299 if ui.verbose or (f in files):
2299 if ui.verbose or (f in files):
2300 warnings.append(_('not removing %s: file still exists\n')
2300 warnings.append(_('not removing %s: file still exists\n')
2301 % uipathfn(f))
2301 % uipathfn(f))
2302 ret = 1
2302 ret = 1
2303 progress.complete()
2303 progress.complete()
2304 else:
2304 else:
2305 list = deleted + clean
2305 list = deleted + clean
2306 progress = ui.makeprogress(_('skipping'),
2306 progress = ui.makeprogress(_('skipping'),
2307 total=(len(modified) + len(added)),
2307 total=(len(modified) + len(added)),
2308 unit=_('files'))
2308 unit=_('files'))
2309 for f in modified:
2309 for f in modified:
2310 progress.increment()
2310 progress.increment()
2311 warnings.append(_('not removing %s: file is modified (use -f'
2311 warnings.append(_('not removing %s: file is modified (use -f'
2312 ' to force removal)\n') % uipathfn(f))
2312 ' to force removal)\n') % uipathfn(f))
2313 ret = 1
2313 ret = 1
2314 for f in added:
2314 for f in added:
2315 progress.increment()
2315 progress.increment()
2316 warnings.append(_("not removing %s: file has been marked for add"
2316 warnings.append(_("not removing %s: file has been marked for add"
2317 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2317 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2318 ret = 1
2318 ret = 1
2319 progress.complete()
2319 progress.complete()
2320
2320
2321 list = sorted(list)
2321 list = sorted(list)
2322 progress = ui.makeprogress(_('deleting'), total=len(list),
2322 progress = ui.makeprogress(_('deleting'), total=len(list),
2323 unit=_('files'))
2323 unit=_('files'))
2324 for f in list:
2324 for f in list:
2325 if ui.verbose or not m.exact(f):
2325 if ui.verbose or not m.exact(f):
2326 progress.increment()
2326 progress.increment()
2327 ui.status(_('removing %s\n') % uipathfn(f),
2327 ui.status(_('removing %s\n') % uipathfn(f),
2328 label='ui.addremove.removed')
2328 label='ui.addremove.removed')
2329 progress.complete()
2329 progress.complete()
2330
2330
2331 if not dryrun:
2331 if not dryrun:
2332 with repo.wlock():
2332 with repo.wlock():
2333 if not after:
2333 if not after:
2334 for f in list:
2334 for f in list:
2335 if f in added:
2335 if f in added:
2336 continue # we never unlink added files on remove
2336 continue # we never unlink added files on remove
2337 rmdir = repo.ui.configbool('experimental',
2337 rmdir = repo.ui.configbool('experimental',
2338 'removeemptydirs')
2338 'removeemptydirs')
2339 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2339 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2340 repo[None].forget(list)
2340 repo[None].forget(list)
2341
2341
2342 if warn:
2342 if warn:
2343 for warning in warnings:
2343 for warning in warnings:
2344 ui.warn(warning)
2344 ui.warn(warning)
2345
2345
2346 return ret
2346 return ret
2347
2347
2348 def _updatecatformatter(fm, ctx, matcher, path, decode):
2348 def _updatecatformatter(fm, ctx, matcher, path, decode):
2349 """Hook for adding data to the formatter used by ``hg cat``.
2349 """Hook for adding data to the formatter used by ``hg cat``.
2350
2350
2351 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2351 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2352 this method first."""
2352 this method first."""
2353 data = ctx[path].data()
2353 data = ctx[path].data()
2354 if decode:
2354 if decode:
2355 data = ctx.repo().wwritedata(path, data)
2355 data = ctx.repo().wwritedata(path, data)
2356 fm.startitem()
2356 fm.startitem()
2357 fm.context(ctx=ctx)
2357 fm.context(ctx=ctx)
2358 fm.write('data', '%s', data)
2358 fm.write('data', '%s', data)
2359 fm.data(path=path)
2359 fm.data(path=path)
2360
2360
2361 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2361 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2362 err = 1
2362 err = 1
2363 opts = pycompat.byteskwargs(opts)
2363 opts = pycompat.byteskwargs(opts)
2364
2364
2365 def write(path):
2365 def write(path):
2366 filename = None
2366 filename = None
2367 if fntemplate:
2367 if fntemplate:
2368 filename = makefilename(ctx, fntemplate,
2368 filename = makefilename(ctx, fntemplate,
2369 pathname=os.path.join(prefix, path))
2369 pathname=os.path.join(prefix, path))
2370 # attempt to create the directory if it does not already exist
2370 # attempt to create the directory if it does not already exist
2371 try:
2371 try:
2372 os.makedirs(os.path.dirname(filename))
2372 os.makedirs(os.path.dirname(filename))
2373 except OSError:
2373 except OSError:
2374 pass
2374 pass
2375 with formatter.maybereopen(basefm, filename) as fm:
2375 with formatter.maybereopen(basefm, filename) as fm:
2376 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2376 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2377
2377
2378 # Automation often uses hg cat on single files, so special case it
2378 # Automation often uses hg cat on single files, so special case it
2379 # for performance to avoid the cost of parsing the manifest.
2379 # for performance to avoid the cost of parsing the manifest.
2380 if len(matcher.files()) == 1 and not matcher.anypats():
2380 if len(matcher.files()) == 1 and not matcher.anypats():
2381 file = matcher.files()[0]
2381 file = matcher.files()[0]
2382 mfl = repo.manifestlog
2382 mfl = repo.manifestlog
2383 mfnode = ctx.manifestnode()
2383 mfnode = ctx.manifestnode()
2384 try:
2384 try:
2385 if mfnode and mfl[mfnode].find(file)[0]:
2385 if mfnode and mfl[mfnode].find(file)[0]:
2386 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2386 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2387 write(file)
2387 write(file)
2388 return 0
2388 return 0
2389 except KeyError:
2389 except KeyError:
2390 pass
2390 pass
2391
2391
2392 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2392 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2393
2393
2394 for abs in ctx.walk(matcher):
2394 for abs in ctx.walk(matcher):
2395 write(abs)
2395 write(abs)
2396 err = 0
2396 err = 0
2397
2397
2398 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2398 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2399 for subpath in sorted(ctx.substate):
2399 for subpath in sorted(ctx.substate):
2400 sub = ctx.sub(subpath)
2400 sub = ctx.sub(subpath)
2401 try:
2401 try:
2402 submatch = matchmod.subdirmatcher(subpath, matcher)
2402 submatch = matchmod.subdirmatcher(subpath, matcher)
2403 subprefix = os.path.join(prefix, subpath)
2403 subprefix = os.path.join(prefix, subpath)
2404 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2404 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2405 **pycompat.strkwargs(opts)):
2405 **pycompat.strkwargs(opts)):
2406 err = 0
2406 err = 0
2407 except error.RepoLookupError:
2407 except error.RepoLookupError:
2408 ui.status(_("skipping missing subrepository: %s\n") %
2408 ui.status(_("skipping missing subrepository: %s\n") %
2409 uipathfn(subpath))
2409 uipathfn(subpath))
2410
2410
2411 return err
2411 return err
2412
2412
2413 def commit(ui, repo, commitfunc, pats, opts):
2413 def commit(ui, repo, commitfunc, pats, opts):
2414 '''commit the specified files or all outstanding changes'''
2414 '''commit the specified files or all outstanding changes'''
2415 date = opts.get('date')
2415 date = opts.get('date')
2416 if date:
2416 if date:
2417 opts['date'] = dateutil.parsedate(date)
2417 opts['date'] = dateutil.parsedate(date)
2418 message = logmessage(ui, opts)
2418 message = logmessage(ui, opts)
2419 matcher = scmutil.match(repo[None], pats, opts)
2419 matcher = scmutil.match(repo[None], pats, opts)
2420
2420
2421 dsguard = None
2421 dsguard = None
2422 # extract addremove carefully -- this function can be called from a command
2422 # extract addremove carefully -- this function can be called from a command
2423 # that doesn't support addremove
2423 # that doesn't support addremove
2424 if opts.get('addremove'):
2424 if opts.get('addremove'):
2425 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2425 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2426 with dsguard or util.nullcontextmanager():
2426 with dsguard or util.nullcontextmanager():
2427 if dsguard:
2427 if dsguard:
2428 relative = scmutil.anypats(pats, opts)
2428 relative = scmutil.anypats(pats, opts)
2429 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2429 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2430 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2430 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2431 raise error.Abort(
2431 raise error.Abort(
2432 _("failed to mark all new/missing files as added/removed"))
2432 _("failed to mark all new/missing files as added/removed"))
2433
2433
2434 return commitfunc(ui, repo, message, matcher, opts)
2434 return commitfunc(ui, repo, message, matcher, opts)
2435
2435
2436 def samefile(f, ctx1, ctx2):
2436 def samefile(f, ctx1, ctx2):
2437 if f in ctx1.manifest():
2437 if f in ctx1.manifest():
2438 a = ctx1.filectx(f)
2438 a = ctx1.filectx(f)
2439 if f in ctx2.manifest():
2439 if f in ctx2.manifest():
2440 b = ctx2.filectx(f)
2440 b = ctx2.filectx(f)
2441 return (not a.cmp(b)
2441 return (not a.cmp(b)
2442 and a.flags() == b.flags())
2442 and a.flags() == b.flags())
2443 else:
2443 else:
2444 return False
2444 return False
2445 else:
2445 else:
2446 return f not in ctx2.manifest()
2446 return f not in ctx2.manifest()
2447
2447
2448 def amend(ui, repo, old, extra, pats, opts):
2448 def amend(ui, repo, old, extra, pats, opts):
2449 # avoid cycle context -> subrepo -> cmdutil
2449 # avoid cycle context -> subrepo -> cmdutil
2450 from . import context
2450 from . import context
2451
2451
2452 # amend will reuse the existing user if not specified, but the obsolete
2452 # amend will reuse the existing user if not specified, but the obsolete
2453 # marker creation requires that the current user's name is specified.
2453 # marker creation requires that the current user's name is specified.
2454 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2454 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2455 ui.username() # raise exception if username not set
2455 ui.username() # raise exception if username not set
2456
2456
2457 ui.note(_('amending changeset %s\n') % old)
2457 ui.note(_('amending changeset %s\n') % old)
2458 base = old.p1()
2458 base = old.p1()
2459
2459
2460 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2460 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2461 # Participating changesets:
2461 # Participating changesets:
2462 #
2462 #
2463 # wctx o - workingctx that contains changes from working copy
2463 # wctx o - workingctx that contains changes from working copy
2464 # | to go into amending commit
2464 # | to go into amending commit
2465 # |
2465 # |
2466 # old o - changeset to amend
2466 # old o - changeset to amend
2467 # |
2467 # |
2468 # base o - first parent of the changeset to amend
2468 # base o - first parent of the changeset to amend
2469 wctx = repo[None]
2469 wctx = repo[None]
2470
2470
2471 # Copy to avoid mutating input
2471 # Copy to avoid mutating input
2472 extra = extra.copy()
2472 extra = extra.copy()
2473 # Update extra dict from amended commit (e.g. to preserve graft
2473 # Update extra dict from amended commit (e.g. to preserve graft
2474 # source)
2474 # source)
2475 extra.update(old.extra())
2475 extra.update(old.extra())
2476
2476
2477 # Also update it from the from the wctx
2477 # Also update it from the from the wctx
2478 extra.update(wctx.extra())
2478 extra.update(wctx.extra())
2479
2479
2480 user = opts.get('user') or old.user()
2480 user = opts.get('user') or old.user()
2481
2481
2482 datemaydiffer = False # date-only change should be ignored?
2482 datemaydiffer = False # date-only change should be ignored?
2483 if opts.get('date') and opts.get('currentdate'):
2483 if opts.get('date') and opts.get('currentdate'):
2484 raise error.Abort(_('--date and --currentdate are mutually '
2484 raise error.Abort(_('--date and --currentdate are mutually '
2485 'exclusive'))
2485 'exclusive'))
2486 if opts.get('date'):
2486 if opts.get('date'):
2487 date = dateutil.parsedate(opts.get('date'))
2487 date = dateutil.parsedate(opts.get('date'))
2488 elif opts.get('currentdate'):
2488 elif opts.get('currentdate'):
2489 date = dateutil.makedate()
2489 date = dateutil.makedate()
2490 elif (ui.configbool('rewrite', 'update-timestamp')
2490 elif (ui.configbool('rewrite', 'update-timestamp')
2491 and opts.get('currentdate') is None):
2491 and opts.get('currentdate') is None):
2492 date = dateutil.makedate()
2492 date = dateutil.makedate()
2493 datemaydiffer = True
2493 datemaydiffer = True
2494 else:
2494 else:
2495 date = old.date()
2495 date = old.date()
2496
2496
2497 if len(old.parents()) > 1:
2497 if len(old.parents()) > 1:
2498 # ctx.files() isn't reliable for merges, so fall back to the
2498 # ctx.files() isn't reliable for merges, so fall back to the
2499 # slower repo.status() method
2499 # slower repo.status() method
2500 files = {fn for st in base.status(old)[:3] for fn in st}
2500 files = {fn for st in base.status(old)[:3] for fn in st}
2501 else:
2501 else:
2502 files = set(old.files())
2502 files = set(old.files())
2503
2503
2504 # add/remove the files to the working copy if the "addremove" option
2504 # add/remove the files to the working copy if the "addremove" option
2505 # was specified.
2505 # was specified.
2506 matcher = scmutil.match(wctx, pats, opts)
2506 matcher = scmutil.match(wctx, pats, opts)
2507 relative = scmutil.anypats(pats, opts)
2507 relative = scmutil.anypats(pats, opts)
2508 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2508 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2509 if (opts.get('addremove')
2509 if (opts.get('addremove')
2510 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2510 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2511 raise error.Abort(
2511 raise error.Abort(
2512 _("failed to mark all new/missing files as added/removed"))
2512 _("failed to mark all new/missing files as added/removed"))
2513
2513
2514 # Check subrepos. This depends on in-place wctx._status update in
2514 # Check subrepos. This depends on in-place wctx._status update in
2515 # subrepo.precommit(). To minimize the risk of this hack, we do
2515 # subrepo.precommit(). To minimize the risk of this hack, we do
2516 # nothing if .hgsub does not exist.
2516 # nothing if .hgsub does not exist.
2517 if '.hgsub' in wctx or '.hgsub' in old:
2517 if '.hgsub' in wctx or '.hgsub' in old:
2518 subs, commitsubs, newsubstate = subrepoutil.precommit(
2518 subs, commitsubs, newsubstate = subrepoutil.precommit(
2519 ui, wctx, wctx._status, matcher)
2519 ui, wctx, wctx._status, matcher)
2520 # amend should abort if commitsubrepos is enabled
2520 # amend should abort if commitsubrepos is enabled
2521 assert not commitsubs
2521 assert not commitsubs
2522 if subs:
2522 if subs:
2523 subrepoutil.writestate(repo, newsubstate)
2523 subrepoutil.writestate(repo, newsubstate)
2524
2524
2525 ms = mergemod.mergestate.read(repo)
2525 ms = mergemod.mergestate.read(repo)
2526 mergeutil.checkunresolved(ms)
2526 mergeutil.checkunresolved(ms)
2527
2527
2528 filestoamend = set(f for f in wctx.files() if matcher(f))
2528 filestoamend = set(f for f in wctx.files() if matcher(f))
2529
2529
2530 changes = (len(filestoamend) > 0)
2530 changes = (len(filestoamend) > 0)
2531 if changes:
2531 if changes:
2532 # Recompute copies (avoid recording a -> b -> a)
2532 # Recompute copies (avoid recording a -> b -> a)
2533 copied = copies.pathcopies(base, wctx, matcher)
2533 copied = copies.pathcopies(base, wctx, matcher)
2534 if old.p2:
2534 if old.p2:
2535 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2535 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2536
2536
2537 # Prune files which were reverted by the updates: if old
2537 # Prune files which were reverted by the updates: if old
2538 # introduced file X and the file was renamed in the working
2538 # introduced file X and the file was renamed in the working
2539 # copy, then those two files are the same and
2539 # copy, then those two files are the same and
2540 # we can discard X from our list of files. Likewise if X
2540 # we can discard X from our list of files. Likewise if X
2541 # was removed, it's no longer relevant. If X is missing (aka
2541 # was removed, it's no longer relevant. If X is missing (aka
2542 # deleted), old X must be preserved.
2542 # deleted), old X must be preserved.
2543 files.update(filestoamend)
2543 files.update(filestoamend)
2544 files = [f for f in files if (not samefile(f, wctx, base)
2544 files = [f for f in files if (not samefile(f, wctx, base)
2545 or f in wctx.deleted())]
2545 or f in wctx.deleted())]
2546
2546
2547 def filectxfn(repo, ctx_, path):
2547 def filectxfn(repo, ctx_, path):
2548 try:
2548 try:
2549 # If the file being considered is not amongst the files
2549 # If the file being considered is not amongst the files
2550 # to be amended, we should return the file context from the
2550 # to be amended, we should return the file context from the
2551 # old changeset. This avoids issues when only some files in
2551 # old changeset. This avoids issues when only some files in
2552 # the working copy are being amended but there are also
2552 # the working copy are being amended but there are also
2553 # changes to other files from the old changeset.
2553 # changes to other files from the old changeset.
2554 if path not in filestoamend:
2554 if path not in filestoamend:
2555 return old.filectx(path)
2555 return old.filectx(path)
2556
2556
2557 # Return None for removed files.
2557 # Return None for removed files.
2558 if path in wctx.removed():
2558 if path in wctx.removed():
2559 return None
2559 return None
2560
2560
2561 fctx = wctx[path]
2561 fctx = wctx[path]
2562 flags = fctx.flags()
2562 flags = fctx.flags()
2563 mctx = context.memfilectx(repo, ctx_,
2563 mctx = context.memfilectx(repo, ctx_,
2564 fctx.path(), fctx.data(),
2564 fctx.path(), fctx.data(),
2565 islink='l' in flags,
2565 islink='l' in flags,
2566 isexec='x' in flags,
2566 isexec='x' in flags,
2567 copysource=copied.get(path))
2567 copysource=copied.get(path))
2568 return mctx
2568 return mctx
2569 except KeyError:
2569 except KeyError:
2570 return None
2570 return None
2571 else:
2571 else:
2572 ui.note(_('copying changeset %s to %s\n') % (old, base))
2572 ui.note(_('copying changeset %s to %s\n') % (old, base))
2573
2573
2574 # Use version of files as in the old cset
2574 # Use version of files as in the old cset
2575 def filectxfn(repo, ctx_, path):
2575 def filectxfn(repo, ctx_, path):
2576 try:
2576 try:
2577 return old.filectx(path)
2577 return old.filectx(path)
2578 except KeyError:
2578 except KeyError:
2579 return None
2579 return None
2580
2580
2581 # See if we got a message from -m or -l, if not, open the editor with
2581 # See if we got a message from -m or -l, if not, open the editor with
2582 # the message of the changeset to amend.
2582 # the message of the changeset to amend.
2583 message = logmessage(ui, opts)
2583 message = logmessage(ui, opts)
2584
2584
2585 editform = mergeeditform(old, 'commit.amend')
2585 editform = mergeeditform(old, 'commit.amend')
2586 editor = getcommiteditor(editform=editform,
2586 editor = getcommiteditor(editform=editform,
2587 **pycompat.strkwargs(opts))
2587 **pycompat.strkwargs(opts))
2588
2588
2589 if not message:
2589 if not message:
2590 editor = getcommiteditor(edit=True, editform=editform)
2590 editor = getcommiteditor(edit=True, editform=editform)
2591 message = old.description()
2591 message = old.description()
2592
2592
2593 pureextra = extra.copy()
2593 pureextra = extra.copy()
2594 extra['amend_source'] = old.hex()
2594 extra['amend_source'] = old.hex()
2595
2595
2596 new = context.memctx(repo,
2596 new = context.memctx(repo,
2597 parents=[base.node(), old.p2().node()],
2597 parents=[base.node(), old.p2().node()],
2598 text=message,
2598 text=message,
2599 files=files,
2599 files=files,
2600 filectxfn=filectxfn,
2600 filectxfn=filectxfn,
2601 user=user,
2601 user=user,
2602 date=date,
2602 date=date,
2603 extra=extra,
2603 extra=extra,
2604 editor=editor)
2604 editor=editor)
2605
2605
2606 newdesc = changelog.stripdesc(new.description())
2606 newdesc = changelog.stripdesc(new.description())
2607 if ((not changes)
2607 if ((not changes)
2608 and newdesc == old.description()
2608 and newdesc == old.description()
2609 and user == old.user()
2609 and user == old.user()
2610 and (date == old.date() or datemaydiffer)
2610 and (date == old.date() or datemaydiffer)
2611 and pureextra == old.extra()):
2611 and pureextra == old.extra()):
2612 # nothing changed. continuing here would create a new node
2612 # nothing changed. continuing here would create a new node
2613 # anyway because of the amend_source noise.
2613 # anyway because of the amend_source noise.
2614 #
2614 #
2615 # This not what we expect from amend.
2615 # This not what we expect from amend.
2616 return old.node()
2616 return old.node()
2617
2617
2618 commitphase = None
2618 commitphase = None
2619 if opts.get('secret'):
2619 if opts.get('secret'):
2620 commitphase = phases.secret
2620 commitphase = phases.secret
2621 newid = repo.commitctx(new)
2621 newid = repo.commitctx(new)
2622
2622
2623 # Reroute the working copy parent to the new changeset
2623 # Reroute the working copy parent to the new changeset
2624 repo.setparents(newid, nullid)
2624 repo.setparents(newid, nullid)
2625 mapping = {old.node(): (newid,)}
2625 mapping = {old.node(): (newid,)}
2626 obsmetadata = None
2626 obsmetadata = None
2627 if opts.get('note'):
2627 if opts.get('note'):
2628 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2628 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2629 backup = ui.configbool('rewrite', 'backup-bundle')
2629 backup = ui.configbool('rewrite', 'backup-bundle')
2630 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2630 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2631 fixphase=True, targetphase=commitphase,
2631 fixphase=True, targetphase=commitphase,
2632 backup=backup)
2632 backup=backup)
2633
2633
2634 # Fixing the dirstate because localrepo.commitctx does not update
2634 # Fixing the dirstate because localrepo.commitctx does not update
2635 # it. This is rather convenient because we did not need to update
2635 # it. This is rather convenient because we did not need to update
2636 # the dirstate for all the files in the new commit which commitctx
2636 # the dirstate for all the files in the new commit which commitctx
2637 # could have done if it updated the dirstate. Now, we can
2637 # could have done if it updated the dirstate. Now, we can
2638 # selectively update the dirstate only for the amended files.
2638 # selectively update the dirstate only for the amended files.
2639 dirstate = repo.dirstate
2639 dirstate = repo.dirstate
2640
2640
2641 # Update the state of the files which were added and
2641 # Update the state of the files which were added and
2642 # and modified in the amend to "normal" in the dirstate.
2642 # and modified in the amend to "normal" in the dirstate.
2643 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2643 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2644 for f in normalfiles:
2644 for f in normalfiles:
2645 dirstate.normal(f)
2645 dirstate.normal(f)
2646
2646
2647 # Update the state of files which were removed in the amend
2647 # Update the state of files which were removed in the amend
2648 # to "removed" in the dirstate.
2648 # to "removed" in the dirstate.
2649 removedfiles = set(wctx.removed()) & filestoamend
2649 removedfiles = set(wctx.removed()) & filestoamend
2650 for f in removedfiles:
2650 for f in removedfiles:
2651 dirstate.drop(f)
2651 dirstate.drop(f)
2652
2652
2653 return newid
2653 return newid
2654
2654
2655 def commiteditor(repo, ctx, subs, editform=''):
2655 def commiteditor(repo, ctx, subs, editform=''):
2656 if ctx.description():
2656 if ctx.description():
2657 return ctx.description()
2657 return ctx.description()
2658 return commitforceeditor(repo, ctx, subs, editform=editform,
2658 return commitforceeditor(repo, ctx, subs, editform=editform,
2659 unchangedmessagedetection=True)
2659 unchangedmessagedetection=True)
2660
2660
2661 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2661 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2662 editform='', unchangedmessagedetection=False):
2662 editform='', unchangedmessagedetection=False):
2663 if not extramsg:
2663 if not extramsg:
2664 extramsg = _("Leave message empty to abort commit.")
2664 extramsg = _("Leave message empty to abort commit.")
2665
2665
2666 forms = [e for e in editform.split('.') if e]
2666 forms = [e for e in editform.split('.') if e]
2667 forms.insert(0, 'changeset')
2667 forms.insert(0, 'changeset')
2668 templatetext = None
2668 templatetext = None
2669 while forms:
2669 while forms:
2670 ref = '.'.join(forms)
2670 ref = '.'.join(forms)
2671 if repo.ui.config('committemplate', ref):
2671 if repo.ui.config('committemplate', ref):
2672 templatetext = committext = buildcommittemplate(
2672 templatetext = committext = buildcommittemplate(
2673 repo, ctx, subs, extramsg, ref)
2673 repo, ctx, subs, extramsg, ref)
2674 break
2674 break
2675 forms.pop()
2675 forms.pop()
2676 else:
2676 else:
2677 committext = buildcommittext(repo, ctx, subs, extramsg)
2677 committext = buildcommittext(repo, ctx, subs, extramsg)
2678
2678
2679 # run editor in the repository root
2679 # run editor in the repository root
2680 olddir = encoding.getcwd()
2680 olddir = encoding.getcwd()
2681 os.chdir(repo.root)
2681 os.chdir(repo.root)
2682
2682
2683 # make in-memory changes visible to external process
2683 # make in-memory changes visible to external process
2684 tr = repo.currenttransaction()
2684 tr = repo.currenttransaction()
2685 repo.dirstate.write(tr)
2685 repo.dirstate.write(tr)
2686 pending = tr and tr.writepending() and repo.root
2686 pending = tr and tr.writepending() and repo.root
2687
2687
2688 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2688 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2689 editform=editform, pending=pending,
2689 editform=editform, pending=pending,
2690 repopath=repo.path, action='commit')
2690 repopath=repo.path, action='commit')
2691 text = editortext
2691 text = editortext
2692
2692
2693 # strip away anything below this special string (used for editors that want
2693 # strip away anything below this special string (used for editors that want
2694 # to display the diff)
2694 # to display the diff)
2695 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2695 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2696 if stripbelow:
2696 if stripbelow:
2697 text = text[:stripbelow.start()]
2697 text = text[:stripbelow.start()]
2698
2698
2699 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2699 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2700 os.chdir(olddir)
2700 os.chdir(olddir)
2701
2701
2702 if finishdesc:
2702 if finishdesc:
2703 text = finishdesc(text)
2703 text = finishdesc(text)
2704 if not text.strip():
2704 if not text.strip():
2705 raise error.Abort(_("empty commit message"))
2705 raise error.Abort(_("empty commit message"))
2706 if unchangedmessagedetection and editortext == templatetext:
2706 if unchangedmessagedetection and editortext == templatetext:
2707 raise error.Abort(_("commit message unchanged"))
2707 raise error.Abort(_("commit message unchanged"))
2708
2708
2709 return text
2709 return text
2710
2710
2711 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2711 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2712 ui = repo.ui
2712 ui = repo.ui
2713 spec = formatter.templatespec(ref, None, None)
2713 spec = formatter.templatespec(ref, None, None)
2714 t = logcmdutil.changesettemplater(ui, repo, spec)
2714 t = logcmdutil.changesettemplater(ui, repo, spec)
2715 t.t.cache.update((k, templater.unquotestring(v))
2715 t.t.cache.update((k, templater.unquotestring(v))
2716 for k, v in repo.ui.configitems('committemplate'))
2716 for k, v in repo.ui.configitems('committemplate'))
2717
2717
2718 if not extramsg:
2718 if not extramsg:
2719 extramsg = '' # ensure that extramsg is string
2719 extramsg = '' # ensure that extramsg is string
2720
2720
2721 ui.pushbuffer()
2721 ui.pushbuffer()
2722 t.show(ctx, extramsg=extramsg)
2722 t.show(ctx, extramsg=extramsg)
2723 return ui.popbuffer()
2723 return ui.popbuffer()
2724
2724
2725 def hgprefix(msg):
2725 def hgprefix(msg):
2726 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2726 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2727
2727
2728 def buildcommittext(repo, ctx, subs, extramsg):
2728 def buildcommittext(repo, ctx, subs, extramsg):
2729 edittext = []
2729 edittext = []
2730 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2730 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2731 if ctx.description():
2731 if ctx.description():
2732 edittext.append(ctx.description())
2732 edittext.append(ctx.description())
2733 edittext.append("")
2733 edittext.append("")
2734 edittext.append("") # Empty line between message and comments.
2734 edittext.append("") # Empty line between message and comments.
2735 edittext.append(hgprefix(_("Enter commit message."
2735 edittext.append(hgprefix(_("Enter commit message."
2736 " Lines beginning with 'HG:' are removed.")))
2736 " Lines beginning with 'HG:' are removed.")))
2737 edittext.append(hgprefix(extramsg))
2737 edittext.append(hgprefix(extramsg))
2738 edittext.append("HG: --")
2738 edittext.append("HG: --")
2739 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2739 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2740 if ctx.p2():
2740 if ctx.p2():
2741 edittext.append(hgprefix(_("branch merge")))
2741 edittext.append(hgprefix(_("branch merge")))
2742 if ctx.branch():
2742 if ctx.branch():
2743 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2743 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2744 if bookmarks.isactivewdirparent(repo):
2744 if bookmarks.isactivewdirparent(repo):
2745 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2745 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2746 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2746 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2747 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2747 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2748 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2748 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2749 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2749 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2750 if not added and not modified and not removed:
2750 if not added and not modified and not removed:
2751 edittext.append(hgprefix(_("no files changed")))
2751 edittext.append(hgprefix(_("no files changed")))
2752 edittext.append("")
2752 edittext.append("")
2753
2753
2754 return "\n".join(edittext)
2754 return "\n".join(edittext)
2755
2755
2756 def commitstatus(repo, node, branch, bheads=None, opts=None):
2756 def commitstatus(repo, node, branch, bheads=None, opts=None):
2757 if opts is None:
2757 if opts is None:
2758 opts = {}
2758 opts = {}
2759 ctx = repo[node]
2759 ctx = repo[node]
2760 parents = ctx.parents()
2760 parents = ctx.parents()
2761
2761
2762 if (not opts.get('amend') and bheads and node not in bheads and not
2762 if (not opts.get('amend') and bheads and node not in bheads and not
2763 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2763 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2764 repo.ui.status(_('created new head\n'))
2764 repo.ui.status(_('created new head\n'))
2765 # The message is not printed for initial roots. For the other
2765 # The message is not printed for initial roots. For the other
2766 # changesets, it is printed in the following situations:
2766 # changesets, it is printed in the following situations:
2767 #
2767 #
2768 # Par column: for the 2 parents with ...
2768 # Par column: for the 2 parents with ...
2769 # N: null or no parent
2769 # N: null or no parent
2770 # B: parent is on another named branch
2770 # B: parent is on another named branch
2771 # C: parent is a regular non head changeset
2771 # C: parent is a regular non head changeset
2772 # H: parent was a branch head of the current branch
2772 # H: parent was a branch head of the current branch
2773 # Msg column: whether we print "created new head" message
2773 # Msg column: whether we print "created new head" message
2774 # In the following, it is assumed that there already exists some
2774 # In the following, it is assumed that there already exists some
2775 # initial branch heads of the current branch, otherwise nothing is
2775 # initial branch heads of the current branch, otherwise nothing is
2776 # printed anyway.
2776 # printed anyway.
2777 #
2777 #
2778 # Par Msg Comment
2778 # Par Msg Comment
2779 # N N y additional topo root
2779 # N N y additional topo root
2780 #
2780 #
2781 # B N y additional branch root
2781 # B N y additional branch root
2782 # C N y additional topo head
2782 # C N y additional topo head
2783 # H N n usual case
2783 # H N n usual case
2784 #
2784 #
2785 # B B y weird additional branch root
2785 # B B y weird additional branch root
2786 # C B y branch merge
2786 # C B y branch merge
2787 # H B n merge with named branch
2787 # H B n merge with named branch
2788 #
2788 #
2789 # C C y additional head from merge
2789 # C C y additional head from merge
2790 # C H n merge with a head
2790 # C H n merge with a head
2791 #
2791 #
2792 # H H n head merge: head count decreases
2792 # H H n head merge: head count decreases
2793
2793
2794 if not opts.get('close_branch'):
2794 if not opts.get('close_branch'):
2795 for r in parents:
2795 for r in parents:
2796 if r.closesbranch() and r.branch() == branch:
2796 if r.closesbranch() and r.branch() == branch:
2797 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2797 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2798
2798
2799 if repo.ui.debugflag:
2799 if repo.ui.debugflag:
2800 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2800 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2801 elif repo.ui.verbose:
2801 elif repo.ui.verbose:
2802 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2802 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2803
2803
2804 def postcommitstatus(repo, pats, opts):
2804 def postcommitstatus(repo, pats, opts):
2805 return repo.status(match=scmutil.match(repo[None], pats, opts))
2805 return repo.status(match=scmutil.match(repo[None], pats, opts))
2806
2806
2807 def revert(ui, repo, ctx, parents, *pats, **opts):
2807 def revert(ui, repo, ctx, parents, *pats, **opts):
2808 opts = pycompat.byteskwargs(opts)
2808 opts = pycompat.byteskwargs(opts)
2809 parent, p2 = parents
2809 parent, p2 = parents
2810 node = ctx.node()
2810 node = ctx.node()
2811
2811
2812 mf = ctx.manifest()
2812 mf = ctx.manifest()
2813 if node == p2:
2813 if node == p2:
2814 parent = p2
2814 parent = p2
2815
2815
2816 # need all matching names in dirstate and manifest of target rev,
2816 # need all matching names in dirstate and manifest of target rev,
2817 # so have to walk both. do not print errors if files exist in one
2817 # so have to walk both. do not print errors if files exist in one
2818 # but not other. in both cases, filesets should be evaluated against
2818 # but not other. in both cases, filesets should be evaluated against
2819 # workingctx to get consistent result (issue4497). this means 'set:**'
2819 # workingctx to get consistent result (issue4497). this means 'set:**'
2820 # cannot be used to select missing files from target rev.
2820 # cannot be used to select missing files from target rev.
2821
2821
2822 # `names` is a mapping for all elements in working copy and target revision
2822 # `names` is a mapping for all elements in working copy and target revision
2823 # The mapping is in the form:
2823 # The mapping is in the form:
2824 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2824 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2825 names = {}
2825 names = {}
2826 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2826 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2827
2827
2828 with repo.wlock():
2828 with repo.wlock():
2829 ## filling of the `names` mapping
2829 ## filling of the `names` mapping
2830 # walk dirstate to fill `names`
2830 # walk dirstate to fill `names`
2831
2831
2832 interactive = opts.get('interactive', False)
2832 interactive = opts.get('interactive', False)
2833 wctx = repo[None]
2833 wctx = repo[None]
2834 m = scmutil.match(wctx, pats, opts)
2834 m = scmutil.match(wctx, pats, opts)
2835
2835
2836 # we'll need this later
2836 # we'll need this later
2837 targetsubs = sorted(s for s in wctx.substate if m(s))
2837 targetsubs = sorted(s for s in wctx.substate if m(s))
2838
2838
2839 if not m.always():
2839 if not m.always():
2840 matcher = matchmod.badmatch(m, lambda x, y: False)
2840 matcher = matchmod.badmatch(m, lambda x, y: False)
2841 for abs in wctx.walk(matcher):
2841 for abs in wctx.walk(matcher):
2842 names[abs] = m.exact(abs)
2842 names[abs] = m.exact(abs)
2843
2843
2844 # walk target manifest to fill `names`
2844 # walk target manifest to fill `names`
2845
2845
2846 def badfn(path, msg):
2846 def badfn(path, msg):
2847 if path in names:
2847 if path in names:
2848 return
2848 return
2849 if path in ctx.substate:
2849 if path in ctx.substate:
2850 return
2850 return
2851 path_ = path + '/'
2851 path_ = path + '/'
2852 for f in names:
2852 for f in names:
2853 if f.startswith(path_):
2853 if f.startswith(path_):
2854 return
2854 return
2855 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2855 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2856
2856
2857 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2857 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2858 if abs not in names:
2858 if abs not in names:
2859 names[abs] = m.exact(abs)
2859 names[abs] = m.exact(abs)
2860
2860
2861 # Find status of all file in `names`.
2861 # Find status of all file in `names`.
2862 m = scmutil.matchfiles(repo, names)
2862 m = scmutil.matchfiles(repo, names)
2863
2863
2864 changes = repo.status(node1=node, match=m,
2864 changes = repo.status(node1=node, match=m,
2865 unknown=True, ignored=True, clean=True)
2865 unknown=True, ignored=True, clean=True)
2866 else:
2866 else:
2867 changes = repo.status(node1=node, match=m)
2867 changes = repo.status(node1=node, match=m)
2868 for kind in changes:
2868 for kind in changes:
2869 for abs in kind:
2869 for abs in kind:
2870 names[abs] = m.exact(abs)
2870 names[abs] = m.exact(abs)
2871
2871
2872 m = scmutil.matchfiles(repo, names)
2872 m = scmutil.matchfiles(repo, names)
2873
2873
2874 modified = set(changes.modified)
2874 modified = set(changes.modified)
2875 added = set(changes.added)
2875 added = set(changes.added)
2876 removed = set(changes.removed)
2876 removed = set(changes.removed)
2877 _deleted = set(changes.deleted)
2877 _deleted = set(changes.deleted)
2878 unknown = set(changes.unknown)
2878 unknown = set(changes.unknown)
2879 unknown.update(changes.ignored)
2879 unknown.update(changes.ignored)
2880 clean = set(changes.clean)
2880 clean = set(changes.clean)
2881 modadded = set()
2881 modadded = set()
2882
2882
2883 # We need to account for the state of the file in the dirstate,
2883 # We need to account for the state of the file in the dirstate,
2884 # even when we revert against something else than parent. This will
2884 # even when we revert against something else than parent. This will
2885 # slightly alter the behavior of revert (doing back up or not, delete
2885 # slightly alter the behavior of revert (doing back up or not, delete
2886 # or just forget etc).
2886 # or just forget etc).
2887 if parent == node:
2887 if parent == node:
2888 dsmodified = modified
2888 dsmodified = modified
2889 dsadded = added
2889 dsadded = added
2890 dsremoved = removed
2890 dsremoved = removed
2891 # store all local modifications, useful later for rename detection
2891 # store all local modifications, useful later for rename detection
2892 localchanges = dsmodified | dsadded
2892 localchanges = dsmodified | dsadded
2893 modified, added, removed = set(), set(), set()
2893 modified, added, removed = set(), set(), set()
2894 else:
2894 else:
2895 changes = repo.status(node1=parent, match=m)
2895 changes = repo.status(node1=parent, match=m)
2896 dsmodified = set(changes.modified)
2896 dsmodified = set(changes.modified)
2897 dsadded = set(changes.added)
2897 dsadded = set(changes.added)
2898 dsremoved = set(changes.removed)
2898 dsremoved = set(changes.removed)
2899 # store all local modifications, useful later for rename detection
2899 # store all local modifications, useful later for rename detection
2900 localchanges = dsmodified | dsadded
2900 localchanges = dsmodified | dsadded
2901
2901
2902 # only take into account for removes between wc and target
2902 # only take into account for removes between wc and target
2903 clean |= dsremoved - removed
2903 clean |= dsremoved - removed
2904 dsremoved &= removed
2904 dsremoved &= removed
2905 # distinct between dirstate remove and other
2905 # distinct between dirstate remove and other
2906 removed -= dsremoved
2906 removed -= dsremoved
2907
2907
2908 modadded = added & dsmodified
2908 modadded = added & dsmodified
2909 added -= modadded
2909 added -= modadded
2910
2910
2911 # tell newly modified apart.
2911 # tell newly modified apart.
2912 dsmodified &= modified
2912 dsmodified &= modified
2913 dsmodified |= modified & dsadded # dirstate added may need backup
2913 dsmodified |= modified & dsadded # dirstate added may need backup
2914 modified -= dsmodified
2914 modified -= dsmodified
2915
2915
2916 # We need to wait for some post-processing to update this set
2916 # We need to wait for some post-processing to update this set
2917 # before making the distinction. The dirstate will be used for
2917 # before making the distinction. The dirstate will be used for
2918 # that purpose.
2918 # that purpose.
2919 dsadded = added
2919 dsadded = added
2920
2920
2921 # in case of merge, files that are actually added can be reported as
2921 # in case of merge, files that are actually added can be reported as
2922 # modified, we need to post process the result
2922 # modified, we need to post process the result
2923 if p2 != nullid:
2923 if p2 != nullid:
2924 mergeadd = set(dsmodified)
2924 mergeadd = set(dsmodified)
2925 for path in dsmodified:
2925 for path in dsmodified:
2926 if path in mf:
2926 if path in mf:
2927 mergeadd.remove(path)
2927 mergeadd.remove(path)
2928 dsadded |= mergeadd
2928 dsadded |= mergeadd
2929 dsmodified -= mergeadd
2929 dsmodified -= mergeadd
2930
2930
2931 # if f is a rename, update `names` to also revert the source
2931 # if f is a rename, update `names` to also revert the source
2932 for f in localchanges:
2932 for f in localchanges:
2933 src = repo.dirstate.copied(f)
2933 src = repo.dirstate.copied(f)
2934 # XXX should we check for rename down to target node?
2934 # XXX should we check for rename down to target node?
2935 if src and src not in names and repo.dirstate[src] == 'r':
2935 if src and src not in names and repo.dirstate[src] == 'r':
2936 dsremoved.add(src)
2936 dsremoved.add(src)
2937 names[src] = True
2937 names[src] = True
2938
2938
2939 # determine the exact nature of the deleted changesets
2939 # determine the exact nature of the deleted changesets
2940 deladded = set(_deleted)
2940 deladded = set(_deleted)
2941 for path in _deleted:
2941 for path in _deleted:
2942 if path in mf:
2942 if path in mf:
2943 deladded.remove(path)
2943 deladded.remove(path)
2944 deleted = _deleted - deladded
2944 deleted = _deleted - deladded
2945
2945
2946 # distinguish between file to forget and the other
2946 # distinguish between file to forget and the other
2947 added = set()
2947 added = set()
2948 for abs in dsadded:
2948 for abs in dsadded:
2949 if repo.dirstate[abs] != 'a':
2949 if repo.dirstate[abs] != 'a':
2950 added.add(abs)
2950 added.add(abs)
2951 dsadded -= added
2951 dsadded -= added
2952
2952
2953 for abs in deladded:
2953 for abs in deladded:
2954 if repo.dirstate[abs] == 'a':
2954 if repo.dirstate[abs] == 'a':
2955 dsadded.add(abs)
2955 dsadded.add(abs)
2956 deladded -= dsadded
2956 deladded -= dsadded
2957
2957
2958 # For files marked as removed, we check if an unknown file is present at
2958 # For files marked as removed, we check if an unknown file is present at
2959 # the same path. If a such file exists it may need to be backed up.
2959 # the same path. If a such file exists it may need to be backed up.
2960 # Making the distinction at this stage helps have simpler backup
2960 # Making the distinction at this stage helps have simpler backup
2961 # logic.
2961 # logic.
2962 removunk = set()
2962 removunk = set()
2963 for abs in removed:
2963 for abs in removed:
2964 target = repo.wjoin(abs)
2964 target = repo.wjoin(abs)
2965 if os.path.lexists(target):
2965 if os.path.lexists(target):
2966 removunk.add(abs)
2966 removunk.add(abs)
2967 removed -= removunk
2967 removed -= removunk
2968
2968
2969 dsremovunk = set()
2969 dsremovunk = set()
2970 for abs in dsremoved:
2970 for abs in dsremoved:
2971 target = repo.wjoin(abs)
2971 target = repo.wjoin(abs)
2972 if os.path.lexists(target):
2972 if os.path.lexists(target):
2973 dsremovunk.add(abs)
2973 dsremovunk.add(abs)
2974 dsremoved -= dsremovunk
2974 dsremoved -= dsremovunk
2975
2975
2976 # action to be actually performed by revert
2976 # action to be actually performed by revert
2977 # (<list of file>, message>) tuple
2977 # (<list of file>, message>) tuple
2978 actions = {'revert': ([], _('reverting %s\n')),
2978 actions = {'revert': ([], _('reverting %s\n')),
2979 'add': ([], _('adding %s\n')),
2979 'add': ([], _('adding %s\n')),
2980 'remove': ([], _('removing %s\n')),
2980 'remove': ([], _('removing %s\n')),
2981 'drop': ([], _('removing %s\n')),
2981 'drop': ([], _('removing %s\n')),
2982 'forget': ([], _('forgetting %s\n')),
2982 'forget': ([], _('forgetting %s\n')),
2983 'undelete': ([], _('undeleting %s\n')),
2983 'undelete': ([], _('undeleting %s\n')),
2984 'noop': (None, _('no changes needed to %s\n')),
2984 'noop': (None, _('no changes needed to %s\n')),
2985 'unknown': (None, _('file not managed: %s\n')),
2985 'unknown': (None, _('file not managed: %s\n')),
2986 }
2986 }
2987
2987
2988 # "constant" that convey the backup strategy.
2988 # "constant" that convey the backup strategy.
2989 # All set to `discard` if `no-backup` is set do avoid checking
2989 # All set to `discard` if `no-backup` is set do avoid checking
2990 # no_backup lower in the code.
2990 # no_backup lower in the code.
2991 # These values are ordered for comparison purposes
2991 # These values are ordered for comparison purposes
2992 backupinteractive = 3 # do backup if interactively modified
2992 backupinteractive = 3 # do backup if interactively modified
2993 backup = 2 # unconditionally do backup
2993 backup = 2 # unconditionally do backup
2994 check = 1 # check if the existing file differs from target
2994 check = 1 # check if the existing file differs from target
2995 discard = 0 # never do backup
2995 discard = 0 # never do backup
2996 if opts.get('no_backup'):
2996 if opts.get('no_backup'):
2997 backupinteractive = backup = check = discard
2997 backupinteractive = backup = check = discard
2998 if interactive:
2998 if interactive:
2999 dsmodifiedbackup = backupinteractive
2999 dsmodifiedbackup = backupinteractive
3000 else:
3000 else:
3001 dsmodifiedbackup = backup
3001 dsmodifiedbackup = backup
3002 tobackup = set()
3002 tobackup = set()
3003
3003
3004 backupanddel = actions['remove']
3004 backupanddel = actions['remove']
3005 if not opts.get('no_backup'):
3005 if not opts.get('no_backup'):
3006 backupanddel = actions['drop']
3006 backupanddel = actions['drop']
3007
3007
3008 disptable = (
3008 disptable = (
3009 # dispatch table:
3009 # dispatch table:
3010 # file state
3010 # file state
3011 # action
3011 # action
3012 # make backup
3012 # make backup
3013
3013
3014 ## Sets that results that will change file on disk
3014 ## Sets that results that will change file on disk
3015 # Modified compared to target, no local change
3015 # Modified compared to target, no local change
3016 (modified, actions['revert'], discard),
3016 (modified, actions['revert'], discard),
3017 # Modified compared to target, but local file is deleted
3017 # Modified compared to target, but local file is deleted
3018 (deleted, actions['revert'], discard),
3018 (deleted, actions['revert'], discard),
3019 # Modified compared to target, local change
3019 # Modified compared to target, local change
3020 (dsmodified, actions['revert'], dsmodifiedbackup),
3020 (dsmodified, actions['revert'], dsmodifiedbackup),
3021 # Added since target
3021 # Added since target
3022 (added, actions['remove'], discard),
3022 (added, actions['remove'], discard),
3023 # Added in working directory
3023 # Added in working directory
3024 (dsadded, actions['forget'], discard),
3024 (dsadded, actions['forget'], discard),
3025 # Added since target, have local modification
3025 # Added since target, have local modification
3026 (modadded, backupanddel, backup),
3026 (modadded, backupanddel, backup),
3027 # Added since target but file is missing in working directory
3027 # Added since target but file is missing in working directory
3028 (deladded, actions['drop'], discard),
3028 (deladded, actions['drop'], discard),
3029 # Removed since target, before working copy parent
3029 # Removed since target, before working copy parent
3030 (removed, actions['add'], discard),
3030 (removed, actions['add'], discard),
3031 # Same as `removed` but an unknown file exists at the same path
3031 # Same as `removed` but an unknown file exists at the same path
3032 (removunk, actions['add'], check),
3032 (removunk, actions['add'], check),
3033 # Removed since targe, marked as such in working copy parent
3033 # Removed since targe, marked as such in working copy parent
3034 (dsremoved, actions['undelete'], discard),
3034 (dsremoved, actions['undelete'], discard),
3035 # Same as `dsremoved` but an unknown file exists at the same path
3035 # Same as `dsremoved` but an unknown file exists at the same path
3036 (dsremovunk, actions['undelete'], check),
3036 (dsremovunk, actions['undelete'], check),
3037 ## the following sets does not result in any file changes
3037 ## the following sets does not result in any file changes
3038 # File with no modification
3038 # File with no modification
3039 (clean, actions['noop'], discard),
3039 (clean, actions['noop'], discard),
3040 # Existing file, not tracked anywhere
3040 # Existing file, not tracked anywhere
3041 (unknown, actions['unknown'], discard),
3041 (unknown, actions['unknown'], discard),
3042 )
3042 )
3043
3043
3044 for abs, exact in sorted(names.items()):
3044 for abs, exact in sorted(names.items()):
3045 # target file to be touch on disk (relative to cwd)
3045 # target file to be touch on disk (relative to cwd)
3046 target = repo.wjoin(abs)
3046 target = repo.wjoin(abs)
3047 # search the entry in the dispatch table.
3047 # search the entry in the dispatch table.
3048 # if the file is in any of these sets, it was touched in the working
3048 # if the file is in any of these sets, it was touched in the working
3049 # directory parent and we are sure it needs to be reverted.
3049 # directory parent and we are sure it needs to be reverted.
3050 for table, (xlist, msg), dobackup in disptable:
3050 for table, (xlist, msg), dobackup in disptable:
3051 if abs not in table:
3051 if abs not in table:
3052 continue
3052 continue
3053 if xlist is not None:
3053 if xlist is not None:
3054 xlist.append(abs)
3054 xlist.append(abs)
3055 if dobackup:
3055 if dobackup:
3056 # If in interactive mode, don't automatically create
3056 # If in interactive mode, don't automatically create
3057 # .orig files (issue4793)
3057 # .orig files (issue4793)
3058 if dobackup == backupinteractive:
3058 if dobackup == backupinteractive:
3059 tobackup.add(abs)
3059 tobackup.add(abs)
3060 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3060 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3061 absbakname = scmutil.backuppath(ui, repo, abs)
3061 absbakname = scmutil.backuppath(ui, repo, abs)
3062 bakname = os.path.relpath(absbakname,
3062 bakname = os.path.relpath(absbakname,
3063 start=repo.root)
3063 start=repo.root)
3064 ui.note(_('saving current version of %s as %s\n') %
3064 ui.note(_('saving current version of %s as %s\n') %
3065 (uipathfn(abs), uipathfn(bakname)))
3065 (uipathfn(abs), uipathfn(bakname)))
3066 if not opts.get('dry_run'):
3066 if not opts.get('dry_run'):
3067 if interactive:
3067 if interactive:
3068 util.copyfile(target, absbakname)
3068 util.copyfile(target, absbakname)
3069 else:
3069 else:
3070 util.rename(target, absbakname)
3070 util.rename(target, absbakname)
3071 if opts.get('dry_run'):
3071 if opts.get('dry_run'):
3072 if ui.verbose or not exact:
3072 if ui.verbose or not exact:
3073 ui.status(msg % uipathfn(abs))
3073 ui.status(msg % uipathfn(abs))
3074 elif exact:
3074 elif exact:
3075 ui.warn(msg % uipathfn(abs))
3075 ui.warn(msg % uipathfn(abs))
3076 break
3076 break
3077
3077
3078 if not opts.get('dry_run'):
3078 if not opts.get('dry_run'):
3079 needdata = ('revert', 'add', 'undelete')
3079 needdata = ('revert', 'add', 'undelete')
3080 oplist = [actions[name][0] for name in needdata]
3080 oplist = [actions[name][0] for name in needdata]
3081 prefetch = scmutil.prefetchfiles
3081 prefetch = scmutil.prefetchfiles
3082 matchfiles = scmutil.matchfiles
3082 matchfiles = scmutil.matchfiles
3083 prefetch(repo, [ctx.rev()],
3083 prefetch(repo, [ctx.rev()],
3084 matchfiles(repo,
3084 matchfiles(repo,
3085 [f for sublist in oplist for f in sublist]))
3085 [f for sublist in oplist for f in sublist]))
3086 match = scmutil.match(repo[None], pats)
3086 match = scmutil.match(repo[None], pats)
3087 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3087 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3088 match, interactive, tobackup)
3088 match, interactive, tobackup)
3089
3089
3090 if targetsubs:
3090 if targetsubs:
3091 # Revert the subrepos on the revert list
3091 # Revert the subrepos on the revert list
3092 for sub in targetsubs:
3092 for sub in targetsubs:
3093 try:
3093 try:
3094 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3094 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3095 **pycompat.strkwargs(opts))
3095 **pycompat.strkwargs(opts))
3096 except KeyError:
3096 except KeyError:
3097 raise error.Abort("subrepository '%s' does not exist in %s!"
3097 raise error.Abort("subrepository '%s' does not exist in %s!"
3098 % (sub, short(ctx.node())))
3098 % (sub, short(ctx.node())))
3099
3099
3100 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3100 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3101 match, interactive=False, tobackup=None):
3101 match, interactive=False, tobackup=None):
3102 """function that actually perform all the actions computed for revert
3102 """function that actually perform all the actions computed for revert
3103
3103
3104 This is an independent function to let extension to plug in and react to
3104 This is an independent function to let extension to plug in and react to
3105 the imminent revert.
3105 the imminent revert.
3106
3106
3107 Make sure you have the working directory locked when calling this function.
3107 Make sure you have the working directory locked when calling this function.
3108 """
3108 """
3109 parent, p2 = parents
3109 parent, p2 = parents
3110 node = ctx.node()
3110 node = ctx.node()
3111 excluded_files = []
3111 excluded_files = []
3112
3112
3113 def checkout(f):
3113 def checkout(f):
3114 fc = ctx[f]
3114 fc = ctx[f]
3115 repo.wwrite(f, fc.data(), fc.flags())
3115 repo.wwrite(f, fc.data(), fc.flags())
3116
3116
3117 def doremove(f):
3117 def doremove(f):
3118 try:
3118 try:
3119 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3119 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3120 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3120 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3121 except OSError:
3121 except OSError:
3122 pass
3122 pass
3123 repo.dirstate.remove(f)
3123 repo.dirstate.remove(f)
3124
3124
3125 def prntstatusmsg(action, f):
3125 def prntstatusmsg(action, f):
3126 exact = names[f]
3126 exact = names[f]
3127 if repo.ui.verbose or not exact:
3127 if repo.ui.verbose or not exact:
3128 repo.ui.status(actions[action][1] % uipathfn(f))
3128 repo.ui.status(actions[action][1] % uipathfn(f))
3129
3129
3130 audit_path = pathutil.pathauditor(repo.root, cached=True)
3130 audit_path = pathutil.pathauditor(repo.root, cached=True)
3131 for f in actions['forget'][0]:
3131 for f in actions['forget'][0]:
3132 if interactive:
3132 if interactive:
3133 choice = repo.ui.promptchoice(
3133 choice = repo.ui.promptchoice(
3134 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3134 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3135 if choice == 0:
3135 if choice == 0:
3136 prntstatusmsg('forget', f)
3136 prntstatusmsg('forget', f)
3137 repo.dirstate.drop(f)
3137 repo.dirstate.drop(f)
3138 else:
3138 else:
3139 excluded_files.append(f)
3139 excluded_files.append(f)
3140 else:
3140 else:
3141 prntstatusmsg('forget', f)
3141 prntstatusmsg('forget', f)
3142 repo.dirstate.drop(f)
3142 repo.dirstate.drop(f)
3143 for f in actions['remove'][0]:
3143 for f in actions['remove'][0]:
3144 audit_path(f)
3144 audit_path(f)
3145 if interactive:
3145 if interactive:
3146 choice = repo.ui.promptchoice(
3146 choice = repo.ui.promptchoice(
3147 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3147 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3148 if choice == 0:
3148 if choice == 0:
3149 prntstatusmsg('remove', f)
3149 prntstatusmsg('remove', f)
3150 doremove(f)
3150 doremove(f)
3151 else:
3151 else:
3152 excluded_files.append(f)
3152 excluded_files.append(f)
3153 else:
3153 else:
3154 prntstatusmsg('remove', f)
3154 prntstatusmsg('remove', f)
3155 doremove(f)
3155 doremove(f)
3156 for f in actions['drop'][0]:
3156 for f in actions['drop'][0]:
3157 audit_path(f)
3157 audit_path(f)
3158 prntstatusmsg('drop', f)
3158 prntstatusmsg('drop', f)
3159 repo.dirstate.remove(f)
3159 repo.dirstate.remove(f)
3160
3160
3161 normal = None
3161 normal = None
3162 if node == parent:
3162 if node == parent:
3163 # We're reverting to our parent. If possible, we'd like status
3163 # We're reverting to our parent. If possible, we'd like status
3164 # to report the file as clean. We have to use normallookup for
3164 # to report the file as clean. We have to use normallookup for
3165 # merges to avoid losing information about merged/dirty files.
3165 # merges to avoid losing information about merged/dirty files.
3166 if p2 != nullid:
3166 if p2 != nullid:
3167 normal = repo.dirstate.normallookup
3167 normal = repo.dirstate.normallookup
3168 else:
3168 else:
3169 normal = repo.dirstate.normal
3169 normal = repo.dirstate.normal
3170
3170
3171 newlyaddedandmodifiedfiles = set()
3171 newlyaddedandmodifiedfiles = set()
3172 if interactive:
3172 if interactive:
3173 # Prompt the user for changes to revert
3173 # Prompt the user for changes to revert
3174 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3174 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3175 m = scmutil.matchfiles(repo, torevert)
3175 m = scmutil.matchfiles(repo, torevert)
3176 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3176 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3177 section='commands',
3177 section='commands',
3178 configprefix='revert.interactive.')
3178 configprefix='revert.interactive.')
3179 diffopts.nodates = True
3179 diffopts.nodates = True
3180 diffopts.git = True
3180 diffopts.git = True
3181 operation = 'apply'
3181 operation = 'apply'
3182 if node == parent:
3182 if node == parent:
3183 if repo.ui.configbool('experimental',
3183 if repo.ui.configbool('experimental',
3184 'revert.interactive.select-to-keep'):
3184 'revert.interactive.select-to-keep'):
3185 operation = 'keep'
3185 operation = 'keep'
3186 else:
3186 else:
3187 operation = 'discard'
3187 operation = 'discard'
3188
3188
3189 if operation == 'apply':
3189 if operation == 'apply':
3190 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3190 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3191 else:
3191 else:
3192 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3192 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3193 originalchunks = patch.parsepatch(diff)
3193 originalchunks = patch.parsepatch(diff)
3194
3194
3195 try:
3195 try:
3196
3196
3197 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3197 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3198 operation=operation)
3198 operation=operation)
3199 if operation == 'discard':
3199 if operation == 'discard':
3200 chunks = patch.reversehunks(chunks)
3200 chunks = patch.reversehunks(chunks)
3201
3201
3202 except error.PatchError as err:
3202 except error.PatchError as err:
3203 raise error.Abort(_('error parsing patch: %s') % err)
3203 raise error.Abort(_('error parsing patch: %s') % err)
3204
3204
3205 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3205 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3206 if tobackup is None:
3206 if tobackup is None:
3207 tobackup = set()
3207 tobackup = set()
3208 # Apply changes
3208 # Apply changes
3209 fp = stringio()
3209 fp = stringio()
3210 # chunks are serialized per file, but files aren't sorted
3210 # chunks are serialized per file, but files aren't sorted
3211 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3211 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3212 prntstatusmsg('revert', f)
3212 prntstatusmsg('revert', f)
3213 files = set()
3213 files = set()
3214 for c in chunks:
3214 for c in chunks:
3215 if ishunk(c):
3215 if ishunk(c):
3216 abs = c.header.filename()
3216 abs = c.header.filename()
3217 # Create a backup file only if this hunk should be backed up
3217 # Create a backup file only if this hunk should be backed up
3218 if c.header.filename() in tobackup:
3218 if c.header.filename() in tobackup:
3219 target = repo.wjoin(abs)
3219 target = repo.wjoin(abs)
3220 bakname = scmutil.backuppath(repo.ui, repo, abs)
3220 bakname = scmutil.backuppath(repo.ui, repo, abs)
3221 util.copyfile(target, bakname)
3221 util.copyfile(target, bakname)
3222 tobackup.remove(abs)
3222 tobackup.remove(abs)
3223 if abs not in files:
3223 if abs not in files:
3224 files.add(abs)
3224 files.add(abs)
3225 if operation == 'keep':
3225 if operation == 'keep':
3226 checkout(abs)
3226 checkout(abs)
3227 c.write(fp)
3227 c.write(fp)
3228 dopatch = fp.tell()
3228 dopatch = fp.tell()
3229 fp.seek(0)
3229 fp.seek(0)
3230 if dopatch:
3230 if dopatch:
3231 try:
3231 try:
3232 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3232 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3233 except error.PatchError as err:
3233 except error.PatchError as err:
3234 raise error.Abort(pycompat.bytestr(err))
3234 raise error.Abort(pycompat.bytestr(err))
3235 del fp
3235 del fp
3236 else:
3236 else:
3237 for f in actions['revert'][0]:
3237 for f in actions['revert'][0]:
3238 prntstatusmsg('revert', f)
3238 prntstatusmsg('revert', f)
3239 checkout(f)
3239 checkout(f)
3240 if normal:
3240 if normal:
3241 normal(f)
3241 normal(f)
3242
3242
3243 for f in actions['add'][0]:
3243 for f in actions['add'][0]:
3244 # Don't checkout modified files, they are already created by the diff
3244 # Don't checkout modified files, they are already created by the diff
3245 if f not in newlyaddedandmodifiedfiles:
3245 if f not in newlyaddedandmodifiedfiles:
3246 prntstatusmsg('add', f)
3246 prntstatusmsg('add', f)
3247 checkout(f)
3247 checkout(f)
3248 repo.dirstate.add(f)
3248 repo.dirstate.add(f)
3249
3249
3250 normal = repo.dirstate.normallookup
3250 normal = repo.dirstate.normallookup
3251 if node == parent and p2 == nullid:
3251 if node == parent and p2 == nullid:
3252 normal = repo.dirstate.normal
3252 normal = repo.dirstate.normal
3253 for f in actions['undelete'][0]:
3253 for f in actions['undelete'][0]:
3254 if interactive:
3254 if interactive:
3255 choice = repo.ui.promptchoice(
3255 choice = repo.ui.promptchoice(
3256 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3256 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3257 if choice == 0:
3257 if choice == 0:
3258 prntstatusmsg('undelete', f)
3258 prntstatusmsg('undelete', f)
3259 checkout(f)
3259 checkout(f)
3260 normal(f)
3260 normal(f)
3261 else:
3261 else:
3262 excluded_files.append(f)
3262 excluded_files.append(f)
3263 else:
3263 else:
3264 prntstatusmsg('undelete', f)
3264 prntstatusmsg('undelete', f)
3265 checkout(f)
3265 checkout(f)
3266 normal(f)
3266 normal(f)
3267
3267
3268 copied = copies.pathcopies(repo[parent], ctx)
3268 copied = copies.pathcopies(repo[parent], ctx)
3269
3269
3270 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3270 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3271 if f in copied:
3271 if f in copied:
3272 repo.dirstate.copy(copied[f], f)
3272 repo.dirstate.copy(copied[f], f)
3273
3273
3274 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3274 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3275 # commands.outgoing. "missing" is "missing" of the result of
3275 # commands.outgoing. "missing" is "missing" of the result of
3276 # "findcommonoutgoing()"
3276 # "findcommonoutgoing()"
3277 outgoinghooks = util.hooks()
3277 outgoinghooks = util.hooks()
3278
3278
3279 # a list of (ui, repo) functions called by commands.summary
3279 # a list of (ui, repo) functions called by commands.summary
3280 summaryhooks = util.hooks()
3280 summaryhooks = util.hooks()
3281
3281
3282 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3282 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3283 #
3283 #
3284 # functions should return tuple of booleans below, if 'changes' is None:
3284 # functions should return tuple of booleans below, if 'changes' is None:
3285 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3285 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3286 #
3286 #
3287 # otherwise, 'changes' is a tuple of tuples below:
3287 # otherwise, 'changes' is a tuple of tuples below:
3288 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3288 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3289 # - (desturl, destbranch, destpeer, outgoing)
3289 # - (desturl, destbranch, destpeer, outgoing)
3290 summaryremotehooks = util.hooks()
3290 summaryremotehooks = util.hooks()
3291
3291
3292 # A list of state files kept by multistep operations like graft.
3292 # A list of state files kept by multistep operations like graft.
3293 # Since graft cannot be aborted, it is considered 'clearable' by update.
3293 # Since graft cannot be aborted, it is considered 'clearable' by update.
3294 # note: bisect is intentionally excluded
3294 # note: bisect is intentionally excluded
3295 # (state file, clearable, allowcommit, error, hint)
3295 # (state file, clearable, allowcommit, error, hint)
3296 unfinishedstates = [
3296 unfinishedstates = [
3297 ('graftstate', True, False, _('graft in progress'),
3297 ('graftstate', True, False, _('graft in progress'),
3298 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3298 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3299 ('updatestate', True, False, _('last update was interrupted'),
3299 ('updatestate', True, False, _('last update was interrupted'),
3300 _("use 'hg update' to get a consistent checkout"))
3300 _("use 'hg update' to get a consistent checkout"))
3301 ]
3301 ]
3302
3302
3303 def checkunfinished(repo, commit=False):
3303 def checkunfinished(repo, commit=False):
3304 '''Look for an unfinished multistep operation, like graft, and abort
3304 '''Look for an unfinished multistep operation, like graft, and abort
3305 if found. It's probably good to check this right before
3305 if found. It's probably good to check this right before
3306 bailifchanged().
3306 bailifchanged().
3307 '''
3307 '''
3308 # Check for non-clearable states first, so things like rebase will take
3308 # Check for non-clearable states first, so things like rebase will take
3309 # precedence over update.
3309 # precedence over update.
3310 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3310 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3311 if clearable or (commit and allowcommit):
3311 if clearable or (commit and allowcommit):
3312 continue
3312 continue
3313 if repo.vfs.exists(f):
3313 if repo.vfs.exists(f):
3314 raise error.Abort(msg, hint=hint)
3314 raise error.Abort(msg, hint=hint)
3315
3315
3316 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3316 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3317 if not clearable or (commit and allowcommit):
3317 if not clearable or (commit and allowcommit):
3318 continue
3318 continue
3319 if repo.vfs.exists(f):
3319 if repo.vfs.exists(f):
3320 raise error.Abort(msg, hint=hint)
3320 raise error.Abort(msg, hint=hint)
3321
3321
3322 def clearunfinished(repo):
3322 def clearunfinished(repo):
3323 '''Check for unfinished operations (as above), and clear the ones
3323 '''Check for unfinished operations (as above), and clear the ones
3324 that are clearable.
3324 that are clearable.
3325 '''
3325 '''
3326 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3326 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3327 if not clearable and repo.vfs.exists(f):
3327 if not clearable and repo.vfs.exists(f):
3328 raise error.Abort(msg, hint=hint)
3328 raise error.Abort(msg, hint=hint)
3329 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3329 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3330 if clearable and repo.vfs.exists(f):
3330 if clearable and repo.vfs.exists(f):
3331 util.unlink(repo.vfs.join(f))
3331 util.unlink(repo.vfs.join(f))
3332
3332
3333 afterresolvedstates = [
3333 afterresolvedstates = [
3334 ('graftstate',
3334 ('graftstate',
3335 _('hg graft --continue')),
3335 _('hg graft --continue')),
3336 ]
3336 ]
3337
3337
3338 def howtocontinue(repo):
3338 def howtocontinue(repo):
3339 '''Check for an unfinished operation and return the command to finish
3339 '''Check for an unfinished operation and return the command to finish
3340 it.
3340 it.
3341
3341
3342 afterresolvedstates tuples define a .hg/{file} and the corresponding
3342 afterresolvedstates tuples define a .hg/{file} and the corresponding
3343 command needed to finish it.
3343 command needed to finish it.
3344
3344
3345 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3345 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3346 a boolean.
3346 a boolean.
3347 '''
3347 '''
3348 contmsg = _("continue: %s")
3348 contmsg = _("continue: %s")
3349 for f, msg in afterresolvedstates:
3349 for f, msg in afterresolvedstates:
3350 if repo.vfs.exists(f):
3350 if repo.vfs.exists(f):
3351 return contmsg % msg, True
3351 return contmsg % msg, True
3352 if repo[None].dirty(missing=True, merge=False, branch=False):
3352 if repo[None].dirty(missing=True, merge=False, branch=False):
3353 return contmsg % _("hg commit"), False
3353 return contmsg % _("hg commit"), False
3354 return None, None
3354 return None, None
3355
3355
3356 def checkafterresolved(repo):
3356 def checkafterresolved(repo):
3357 '''Inform the user about the next action after completing hg resolve
3357 '''Inform the user about the next action after completing hg resolve
3358
3358
3359 If there's a matching afterresolvedstates, howtocontinue will yield
3359 If there's a matching afterresolvedstates, howtocontinue will yield
3360 repo.ui.warn as the reporter.
3360 repo.ui.warn as the reporter.
3361
3361
3362 Otherwise, it will yield repo.ui.note.
3362 Otherwise, it will yield repo.ui.note.
3363 '''
3363 '''
3364 msg, warning = howtocontinue(repo)
3364 msg, warning = howtocontinue(repo)
3365 if msg is not None:
3365 if msg is not None:
3366 if warning:
3366 if warning:
3367 repo.ui.warn("%s\n" % msg)
3367 repo.ui.warn("%s\n" % msg)
3368 else:
3368 else:
3369 repo.ui.note("%s\n" % msg)
3369 repo.ui.note("%s\n" % msg)
3370
3370
3371 def wrongtooltocontinue(repo, task):
3371 def wrongtooltocontinue(repo, task):
3372 '''Raise an abort suggesting how to properly continue if there is an
3372 '''Raise an abort suggesting how to properly continue if there is an
3373 active task.
3373 active task.
3374
3374
3375 Uses howtocontinue() to find the active task.
3375 Uses howtocontinue() to find the active task.
3376
3376
3377 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3377 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3378 a hint.
3378 a hint.
3379 '''
3379 '''
3380 after = howtocontinue(repo)
3380 after = howtocontinue(repo)
3381 hint = None
3381 hint = None
3382 if after[1]:
3382 if after[1]:
3383 hint = after[0]
3383 hint = after[0]
3384 raise error.Abort(_('no %s in progress') % task, hint=hint)
3384 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,698 +1,699 b''
1 # parser.py - simple top-down operator precedence parser for mercurial
1 # parser.py - simple top-down operator precedence parser for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 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 # see http://effbot.org/zone/simple-top-down-parsing.htm and
8 # see http://effbot.org/zone/simple-top-down-parsing.htm and
9 # http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing/
9 # http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing/
10 # for background
10 # for background
11
11
12 # takes a tokenizer and elements
12 # takes a tokenizer and elements
13 # tokenizer is an iterator that returns (type, value, pos) tuples
13 # tokenizer is an iterator that returns (type, value, pos) tuples
14 # elements is a mapping of types to binding strength, primary, prefix, infix
14 # elements is a mapping of types to binding strength, primary, prefix, infix
15 # and suffix actions
15 # and suffix actions
16 # an action is a tree node name, a tree label, and an optional match
16 # an action is a tree node name, a tree label, and an optional match
17 # __call__(program) parses program into a labeled tree
17 # __call__(program) parses program into a labeled tree
18
18
19 from __future__ import absolute_import, print_function
19 from __future__ import absolute_import, print_function
20
20
21 from .i18n import _
21 from .i18n import _
22 from . import (
22 from . import (
23 error,
23 error,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 )
26 )
27 from .utils import (
27 from .utils import (
28 stringutil,
28 stringutil,
29 )
29 )
30
30
31 class parser(object):
31 class parser(object):
32 def __init__(self, elements, methods=None):
32 def __init__(self, elements, methods=None):
33 self._elements = elements
33 self._elements = elements
34 self._methods = methods
34 self._methods = methods
35 self.current = None
35 self.current = None
36 def _advance(self):
36 def _advance(self):
37 'advance the tokenizer'
37 'advance the tokenizer'
38 t = self.current
38 t = self.current
39 self.current = next(self._iter, None)
39 self.current = next(self._iter, None)
40 return t
40 return t
41 def _hasnewterm(self):
41 def _hasnewterm(self):
42 'True if next token may start new term'
42 'True if next token may start new term'
43 return any(self._elements[self.current[0]][1:3])
43 return any(self._elements[self.current[0]][1:3])
44 def _match(self, m):
44 def _match(self, m):
45 'make sure the tokenizer matches an end condition'
45 'make sure the tokenizer matches an end condition'
46 if self.current[0] != m:
46 if self.current[0] != m:
47 raise error.ParseError(_("unexpected token: %s") % self.current[0],
47 raise error.ParseError(_("unexpected token: %s") % self.current[0],
48 self.current[2])
48 self.current[2])
49 self._advance()
49 self._advance()
50 def _parseoperand(self, bind, m=None):
50 def _parseoperand(self, bind, m=None):
51 'gather right-hand-side operand until an end condition or binding met'
51 'gather right-hand-side operand until an end condition or binding met'
52 if m and self.current[0] == m:
52 if m and self.current[0] == m:
53 expr = None
53 expr = None
54 else:
54 else:
55 expr = self._parse(bind)
55 expr = self._parse(bind)
56 if m:
56 if m:
57 self._match(m)
57 self._match(m)
58 return expr
58 return expr
59 def _parse(self, bind=0):
59 def _parse(self, bind=0):
60 token, value, pos = self._advance()
60 token, value, pos = self._advance()
61 # handle prefix rules on current token, take as primary if unambiguous
61 # handle prefix rules on current token, take as primary if unambiguous
62 primary, prefix = self._elements[token][1:3]
62 primary, prefix = self._elements[token][1:3]
63 if primary and not (prefix and self._hasnewterm()):
63 if primary and not (prefix and self._hasnewterm()):
64 expr = (primary, value)
64 expr = (primary, value)
65 elif prefix:
65 elif prefix:
66 expr = (prefix[0], self._parseoperand(*prefix[1:]))
66 expr = (prefix[0], self._parseoperand(*prefix[1:]))
67 else:
67 else:
68 raise error.ParseError(_("not a prefix: %s") % token, pos)
68 raise error.ParseError(_("not a prefix: %s") % token, pos)
69 # gather tokens until we meet a lower binding strength
69 # gather tokens until we meet a lower binding strength
70 while bind < self._elements[self.current[0]][0]:
70 while bind < self._elements[self.current[0]][0]:
71 token, value, pos = self._advance()
71 token, value, pos = self._advance()
72 # handle infix rules, take as suffix if unambiguous
72 # handle infix rules, take as suffix if unambiguous
73 infix, suffix = self._elements[token][3:]
73 infix, suffix = self._elements[token][3:]
74 if suffix and not (infix and self._hasnewterm()):
74 if suffix and not (infix and self._hasnewterm()):
75 expr = (suffix, expr)
75 expr = (suffix, expr)
76 elif infix:
76 elif infix:
77 expr = (infix[0], expr, self._parseoperand(*infix[1:]))
77 expr = (infix[0], expr, self._parseoperand(*infix[1:]))
78 else:
78 else:
79 raise error.ParseError(_("not an infix: %s") % token, pos)
79 raise error.ParseError(_("not an infix: %s") % token, pos)
80 return expr
80 return expr
81 def parse(self, tokeniter):
81 def parse(self, tokeniter):
82 'generate a parse tree from tokens'
82 'generate a parse tree from tokens'
83 self._iter = tokeniter
83 self._iter = tokeniter
84 self._advance()
84 self._advance()
85 res = self._parse()
85 res = self._parse()
86 token, value, pos = self.current
86 token, value, pos = self.current
87 return res, pos
87 return res, pos
88 def eval(self, tree):
88 def eval(self, tree):
89 'recursively evaluate a parse tree using node methods'
89 'recursively evaluate a parse tree using node methods'
90 if not isinstance(tree, tuple):
90 if not isinstance(tree, tuple):
91 return tree
91 return tree
92 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
92 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
93 def __call__(self, tokeniter):
93 def __call__(self, tokeniter):
94 'parse tokens into a parse tree and evaluate if methods given'
94 'parse tokens into a parse tree and evaluate if methods given'
95 t = self.parse(tokeniter)
95 t = self.parse(tokeniter)
96 if self._methods:
96 if self._methods:
97 return self.eval(t)
97 return self.eval(t)
98 return t
98 return t
99
99
100 def splitargspec(spec):
100 def splitargspec(spec):
101 """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
101 """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
102
102
103 >>> splitargspec(b'')
103 >>> splitargspec(b'')
104 ([], None, [], None)
104 ([], None, [], None)
105 >>> splitargspec(b'foo bar')
105 >>> splitargspec(b'foo bar')
106 ([], None, ['foo', 'bar'], None)
106 ([], None, ['foo', 'bar'], None)
107 >>> splitargspec(b'foo *bar baz **qux')
107 >>> splitargspec(b'foo *bar baz **qux')
108 (['foo'], 'bar', ['baz'], 'qux')
108 (['foo'], 'bar', ['baz'], 'qux')
109 >>> splitargspec(b'*foo')
109 >>> splitargspec(b'*foo')
110 ([], 'foo', [], None)
110 ([], 'foo', [], None)
111 >>> splitargspec(b'**foo')
111 >>> splitargspec(b'**foo')
112 ([], None, [], 'foo')
112 ([], None, [], 'foo')
113 """
113 """
114 optkey = None
114 optkey = None
115 pre, sep, post = spec.partition('**')
115 pre, sep, post = spec.partition('**')
116 if sep:
116 if sep:
117 posts = post.split()
117 posts = post.split()
118 if not posts:
118 if not posts:
119 raise error.ProgrammingError('no **optkey name provided')
119 raise error.ProgrammingError('no **optkey name provided')
120 if len(posts) > 1:
120 if len(posts) > 1:
121 raise error.ProgrammingError('excessive **optkey names provided')
121 raise error.ProgrammingError('excessive **optkey names provided')
122 optkey = posts[0]
122 optkey = posts[0]
123
123
124 pre, sep, post = pre.partition('*')
124 pre, sep, post = pre.partition('*')
125 pres = pre.split()
125 pres = pre.split()
126 posts = post.split()
126 posts = post.split()
127 if sep:
127 if sep:
128 if not posts:
128 if not posts:
129 raise error.ProgrammingError('no *varkey name provided')
129 raise error.ProgrammingError('no *varkey name provided')
130 return pres, posts[0], posts[1:], optkey
130 return pres, posts[0], posts[1:], optkey
131 return [], None, pres, optkey
131 return [], None, pres, optkey
132
132
133 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
133 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
134 """Build dict from list containing positional and keyword arguments
134 """Build dict from list containing positional and keyword arguments
135
135
136 Arguments are specified by a tuple of ``(poskeys, varkey, keys, optkey)``
136 Arguments are specified by a tuple of ``(poskeys, varkey, keys, optkey)``
137 where
137 where
138
138
139 - ``poskeys``: list of names of positional arguments
139 - ``poskeys``: list of names of positional arguments
140 - ``varkey``: optional argument name that takes up remainder
140 - ``varkey``: optional argument name that takes up remainder
141 - ``keys``: list of names that can be either positional or keyword arguments
141 - ``keys``: list of names that can be either positional or keyword arguments
142 - ``optkey``: optional argument name that takes up excess keyword arguments
142 - ``optkey``: optional argument name that takes up excess keyword arguments
143
143
144 If ``varkey`` specified, all ``keys`` must be given as keyword arguments.
144 If ``varkey`` specified, all ``keys`` must be given as keyword arguments.
145
145
146 Invalid keywords, too few positional arguments, or too many positional
146 Invalid keywords, too few positional arguments, or too many positional
147 arguments are rejected, but missing keyword arguments are just omitted.
147 arguments are rejected, but missing keyword arguments are just omitted.
148 """
148 """
149 poskeys, varkey, keys, optkey = argspec
149 poskeys, varkey, keys, optkey = argspec
150 kwstart = next((i for i, x in enumerate(trees) if x[0] == keyvaluenode),
150 kwstart = next((i for i, x in enumerate(trees)
151 if x and x[0] == keyvaluenode),
151 len(trees))
152 len(trees))
152 if kwstart < len(poskeys):
153 if kwstart < len(poskeys):
153 raise error.ParseError(_("%(func)s takes at least %(nargs)d positional "
154 raise error.ParseError(_("%(func)s takes at least %(nargs)d positional "
154 "arguments")
155 "arguments")
155 % {'func': funcname, 'nargs': len(poskeys)})
156 % {'func': funcname, 'nargs': len(poskeys)})
156 if not varkey and kwstart > len(poskeys) + len(keys):
157 if not varkey and kwstart > len(poskeys) + len(keys):
157 raise error.ParseError(_("%(func)s takes at most %(nargs)d positional "
158 raise error.ParseError(_("%(func)s takes at most %(nargs)d positional "
158 "arguments")
159 "arguments")
159 % {'func': funcname,
160 % {'func': funcname,
160 'nargs': len(poskeys) + len(keys)})
161 'nargs': len(poskeys) + len(keys)})
161 args = util.sortdict()
162 args = util.sortdict()
162 # consume positional arguments
163 # consume positional arguments
163 for k, x in zip(poskeys, trees[:kwstart]):
164 for k, x in zip(poskeys, trees[:kwstart]):
164 args[k] = x
165 args[k] = x
165 if varkey:
166 if varkey:
166 args[varkey] = trees[len(args):kwstart]
167 args[varkey] = trees[len(args):kwstart]
167 else:
168 else:
168 for k, x in zip(keys, trees[len(args):kwstart]):
169 for k, x in zip(keys, trees[len(args):kwstart]):
169 args[k] = x
170 args[k] = x
170 # remainder should be keyword arguments
171 # remainder should be keyword arguments
171 if optkey:
172 if optkey:
172 args[optkey] = util.sortdict()
173 args[optkey] = util.sortdict()
173 for x in trees[kwstart:]:
174 for x in trees[kwstart:]:
174 if x[0] != keyvaluenode or x[1][0] != keynode:
175 if not x or x[0] != keyvaluenode or x[1][0] != keynode:
175 raise error.ParseError(_("%(func)s got an invalid argument")
176 raise error.ParseError(_("%(func)s got an invalid argument")
176 % {'func': funcname})
177 % {'func': funcname})
177 k = x[1][1]
178 k = x[1][1]
178 if k in keys:
179 if k in keys:
179 d = args
180 d = args
180 elif not optkey:
181 elif not optkey:
181 raise error.ParseError(_("%(func)s got an unexpected keyword "
182 raise error.ParseError(_("%(func)s got an unexpected keyword "
182 "argument '%(key)s'")
183 "argument '%(key)s'")
183 % {'func': funcname, 'key': k})
184 % {'func': funcname, 'key': k})
184 else:
185 else:
185 d = args[optkey]
186 d = args[optkey]
186 if k in d:
187 if k in d:
187 raise error.ParseError(_("%(func)s got multiple values for keyword "
188 raise error.ParseError(_("%(func)s got multiple values for keyword "
188 "argument '%(key)s'")
189 "argument '%(key)s'")
189 % {'func': funcname, 'key': k})
190 % {'func': funcname, 'key': k})
190 d[k] = x[2]
191 d[k] = x[2]
191 return args
192 return args
192
193
193 def unescapestr(s):
194 def unescapestr(s):
194 try:
195 try:
195 return stringutil.unescapestr(s)
196 return stringutil.unescapestr(s)
196 except ValueError as e:
197 except ValueError as e:
197 # mangle Python's exception into our format
198 # mangle Python's exception into our format
198 raise error.ParseError(pycompat.bytestr(e).lower())
199 raise error.ParseError(pycompat.bytestr(e).lower())
199
200
200 def _prettyformat(tree, leafnodes, level, lines):
201 def _prettyformat(tree, leafnodes, level, lines):
201 if not isinstance(tree, tuple):
202 if not isinstance(tree, tuple):
202 lines.append((level, stringutil.pprint(tree)))
203 lines.append((level, stringutil.pprint(tree)))
203 elif tree[0] in leafnodes:
204 elif tree[0] in leafnodes:
204 rs = map(stringutil.pprint, tree[1:])
205 rs = map(stringutil.pprint, tree[1:])
205 lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
206 lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
206 else:
207 else:
207 lines.append((level, '(%s' % tree[0]))
208 lines.append((level, '(%s' % tree[0]))
208 for s in tree[1:]:
209 for s in tree[1:]:
209 _prettyformat(s, leafnodes, level + 1, lines)
210 _prettyformat(s, leafnodes, level + 1, lines)
210 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
211 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
211
212
212 def prettyformat(tree, leafnodes):
213 def prettyformat(tree, leafnodes):
213 lines = []
214 lines = []
214 _prettyformat(tree, leafnodes, 0, lines)
215 _prettyformat(tree, leafnodes, 0, lines)
215 output = '\n'.join((' ' * l + s) for l, s in lines)
216 output = '\n'.join((' ' * l + s) for l, s in lines)
216 return output
217 return output
217
218
218 def simplifyinfixops(tree, targetnodes):
219 def simplifyinfixops(tree, targetnodes):
219 """Flatten chained infix operations to reduce usage of Python stack
220 """Flatten chained infix operations to reduce usage of Python stack
220
221
221 >>> from . import pycompat
222 >>> from . import pycompat
222 >>> def f(tree):
223 >>> def f(tree):
223 ... s = prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
224 ... s = prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
224 ... print(pycompat.sysstr(s))
225 ... print(pycompat.sysstr(s))
225 >>> f((b'or',
226 >>> f((b'or',
226 ... (b'or',
227 ... (b'or',
227 ... (b'symbol', b'1'),
228 ... (b'symbol', b'1'),
228 ... (b'symbol', b'2')),
229 ... (b'symbol', b'2')),
229 ... (b'symbol', b'3')))
230 ... (b'symbol', b'3')))
230 (or
231 (or
231 (symbol '1')
232 (symbol '1')
232 (symbol '2')
233 (symbol '2')
233 (symbol '3'))
234 (symbol '3'))
234 >>> f((b'func',
235 >>> f((b'func',
235 ... (b'symbol', b'p1'),
236 ... (b'symbol', b'p1'),
236 ... (b'or',
237 ... (b'or',
237 ... (b'or',
238 ... (b'or',
238 ... (b'func',
239 ... (b'func',
239 ... (b'symbol', b'sort'),
240 ... (b'symbol', b'sort'),
240 ... (b'list',
241 ... (b'list',
241 ... (b'or',
242 ... (b'or',
242 ... (b'or',
243 ... (b'or',
243 ... (b'symbol', b'1'),
244 ... (b'symbol', b'1'),
244 ... (b'symbol', b'2')),
245 ... (b'symbol', b'2')),
245 ... (b'symbol', b'3')),
246 ... (b'symbol', b'3')),
246 ... (b'negate',
247 ... (b'negate',
247 ... (b'symbol', b'rev')))),
248 ... (b'symbol', b'rev')))),
248 ... (b'and',
249 ... (b'and',
249 ... (b'symbol', b'4'),
250 ... (b'symbol', b'4'),
250 ... (b'group',
251 ... (b'group',
251 ... (b'or',
252 ... (b'or',
252 ... (b'or',
253 ... (b'or',
253 ... (b'symbol', b'5'),
254 ... (b'symbol', b'5'),
254 ... (b'symbol', b'6')),
255 ... (b'symbol', b'6')),
255 ... (b'symbol', b'7'))))),
256 ... (b'symbol', b'7'))))),
256 ... (b'symbol', b'8'))))
257 ... (b'symbol', b'8'))))
257 (func
258 (func
258 (symbol 'p1')
259 (symbol 'p1')
259 (or
260 (or
260 (func
261 (func
261 (symbol 'sort')
262 (symbol 'sort')
262 (list
263 (list
263 (or
264 (or
264 (symbol '1')
265 (symbol '1')
265 (symbol '2')
266 (symbol '2')
266 (symbol '3'))
267 (symbol '3'))
267 (negate
268 (negate
268 (symbol 'rev'))))
269 (symbol 'rev'))))
269 (and
270 (and
270 (symbol '4')
271 (symbol '4')
271 (group
272 (group
272 (or
273 (or
273 (symbol '5')
274 (symbol '5')
274 (symbol '6')
275 (symbol '6')
275 (symbol '7'))))
276 (symbol '7'))))
276 (symbol '8')))
277 (symbol '8')))
277 """
278 """
278 if not isinstance(tree, tuple):
279 if not isinstance(tree, tuple):
279 return tree
280 return tree
280 op = tree[0]
281 op = tree[0]
281 if op not in targetnodes:
282 if op not in targetnodes:
282 return (op,) + tuple(simplifyinfixops(x, targetnodes) for x in tree[1:])
283 return (op,) + tuple(simplifyinfixops(x, targetnodes) for x in tree[1:])
283
284
284 # walk down left nodes taking each right node. no recursion to left nodes
285 # walk down left nodes taking each right node. no recursion to left nodes
285 # because infix operators are left-associative, i.e. left tree is deep.
286 # because infix operators are left-associative, i.e. left tree is deep.
286 # e.g. '1 + 2 + 3' -> (+ (+ 1 2) 3) -> (+ 1 2 3)
287 # e.g. '1 + 2 + 3' -> (+ (+ 1 2) 3) -> (+ 1 2 3)
287 simplified = []
288 simplified = []
288 x = tree
289 x = tree
289 while x[0] == op:
290 while x[0] == op:
290 l, r = x[1:]
291 l, r = x[1:]
291 simplified.append(simplifyinfixops(r, targetnodes))
292 simplified.append(simplifyinfixops(r, targetnodes))
292 x = l
293 x = l
293 simplified.append(simplifyinfixops(x, targetnodes))
294 simplified.append(simplifyinfixops(x, targetnodes))
294 simplified.append(op)
295 simplified.append(op)
295 return tuple(reversed(simplified))
296 return tuple(reversed(simplified))
296
297
297 def _buildtree(template, placeholder, replstack):
298 def _buildtree(template, placeholder, replstack):
298 if template == placeholder:
299 if template == placeholder:
299 return replstack.pop()
300 return replstack.pop()
300 if not isinstance(template, tuple):
301 if not isinstance(template, tuple):
301 return template
302 return template
302 return tuple(_buildtree(x, placeholder, replstack) for x in template)
303 return tuple(_buildtree(x, placeholder, replstack) for x in template)
303
304
304 def buildtree(template, placeholder, *repls):
305 def buildtree(template, placeholder, *repls):
305 """Create new tree by substituting placeholders by replacements
306 """Create new tree by substituting placeholders by replacements
306
307
307 >>> _ = (b'symbol', b'_')
308 >>> _ = (b'symbol', b'_')
308 >>> def f(template, *repls):
309 >>> def f(template, *repls):
309 ... return buildtree(template, _, *repls)
310 ... return buildtree(template, _, *repls)
310 >>> f((b'func', (b'symbol', b'only'), (b'list', _, _)),
311 >>> f((b'func', (b'symbol', b'only'), (b'list', _, _)),
311 ... ('symbol', '1'), ('symbol', '2'))
312 ... ('symbol', '1'), ('symbol', '2'))
312 ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
313 ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
313 >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
314 >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
314 ('and', ('symbol', '1'), ('not', ('symbol', '2')))
315 ('and', ('symbol', '1'), ('not', ('symbol', '2')))
315 """
316 """
316 if not isinstance(placeholder, tuple):
317 if not isinstance(placeholder, tuple):
317 raise error.ProgrammingError('placeholder must be a node tuple')
318 raise error.ProgrammingError('placeholder must be a node tuple')
318 replstack = list(reversed(repls))
319 replstack = list(reversed(repls))
319 r = _buildtree(template, placeholder, replstack)
320 r = _buildtree(template, placeholder, replstack)
320 if replstack:
321 if replstack:
321 raise error.ProgrammingError('too many replacements')
322 raise error.ProgrammingError('too many replacements')
322 return r
323 return r
323
324
324 def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
325 def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
325 if pattern == tree:
326 if pattern == tree:
326 return True
327 return True
327 if not isinstance(pattern, tuple) or not isinstance(tree, tuple):
328 if not isinstance(pattern, tuple) or not isinstance(tree, tuple):
328 return False
329 return False
329 if pattern == placeholder and tree[0] not in incompletenodes:
330 if pattern == placeholder and tree[0] not in incompletenodes:
330 matches.append(tree)
331 matches.append(tree)
331 return True
332 return True
332 if len(pattern) != len(tree):
333 if len(pattern) != len(tree):
333 return False
334 return False
334 return all(_matchtree(p, x, placeholder, incompletenodes, matches)
335 return all(_matchtree(p, x, placeholder, incompletenodes, matches)
335 for p, x in zip(pattern, tree))
336 for p, x in zip(pattern, tree))
336
337
337 def matchtree(pattern, tree, placeholder=None, incompletenodes=()):
338 def matchtree(pattern, tree, placeholder=None, incompletenodes=()):
338 """If a tree matches the pattern, return a list of the tree and nodes
339 """If a tree matches the pattern, return a list of the tree and nodes
339 matched with the placeholder; Otherwise None
340 matched with the placeholder; Otherwise None
340
341
341 >>> def f(pattern, tree):
342 >>> def f(pattern, tree):
342 ... m = matchtree(pattern, tree, _, {b'keyvalue', b'list'})
343 ... m = matchtree(pattern, tree, _, {b'keyvalue', b'list'})
343 ... if m:
344 ... if m:
344 ... return m[1:]
345 ... return m[1:]
345
346
346 >>> _ = (b'symbol', b'_')
347 >>> _ = (b'symbol', b'_')
347 >>> f((b'func', (b'symbol', b'ancestors'), _),
348 >>> f((b'func', (b'symbol', b'ancestors'), _),
348 ... (b'func', (b'symbol', b'ancestors'), (b'symbol', b'1')))
349 ... (b'func', (b'symbol', b'ancestors'), (b'symbol', b'1')))
349 [('symbol', '1')]
350 [('symbol', '1')]
350 >>> f((b'func', (b'symbol', b'ancestors'), _),
351 >>> f((b'func', (b'symbol', b'ancestors'), _),
351 ... (b'func', (b'symbol', b'ancestors'), None))
352 ... (b'func', (b'symbol', b'ancestors'), None))
352 >>> f((b'range', (b'dagrange', _, _), _),
353 >>> f((b'range', (b'dagrange', _, _), _),
353 ... (b'range',
354 ... (b'range',
354 ... (b'dagrange', (b'symbol', b'1'), (b'symbol', b'2')),
355 ... (b'dagrange', (b'symbol', b'1'), (b'symbol', b'2')),
355 ... (b'symbol', b'3')))
356 ... (b'symbol', b'3')))
356 [('symbol', '1'), ('symbol', '2'), ('symbol', '3')]
357 [('symbol', '1'), ('symbol', '2'), ('symbol', '3')]
357
358
358 The placeholder does not match the specified incomplete nodes because
359 The placeholder does not match the specified incomplete nodes because
359 an incomplete node (e.g. argument list) cannot construct an expression.
360 an incomplete node (e.g. argument list) cannot construct an expression.
360
361
361 >>> f((b'func', (b'symbol', b'ancestors'), _),
362 >>> f((b'func', (b'symbol', b'ancestors'), _),
362 ... (b'func', (b'symbol', b'ancestors'),
363 ... (b'func', (b'symbol', b'ancestors'),
363 ... (b'list', (b'symbol', b'1'), (b'symbol', b'2'))))
364 ... (b'list', (b'symbol', b'1'), (b'symbol', b'2'))))
364
365
365 The placeholder may be omitted, but which shouldn't match a None node.
366 The placeholder may be omitted, but which shouldn't match a None node.
366
367
367 >>> _ = None
368 >>> _ = None
368 >>> f((b'func', (b'symbol', b'ancestors'), None),
369 >>> f((b'func', (b'symbol', b'ancestors'), None),
369 ... (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
370 ... (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
370 """
371 """
371 if placeholder is not None and not isinstance(placeholder, tuple):
372 if placeholder is not None and not isinstance(placeholder, tuple):
372 raise error.ProgrammingError('placeholder must be a node tuple')
373 raise error.ProgrammingError('placeholder must be a node tuple')
373 matches = [tree]
374 matches = [tree]
374 if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
375 if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
375 return matches
376 return matches
376
377
377 def parseerrordetail(inst):
378 def parseerrordetail(inst):
378 """Compose error message from specified ParseError object
379 """Compose error message from specified ParseError object
379 """
380 """
380 if len(inst.args) > 1:
381 if len(inst.args) > 1:
381 return _('at %d: %s') % (inst.args[1], inst.args[0])
382 return _('at %d: %s') % (inst.args[1], inst.args[0])
382 else:
383 else:
383 return inst.args[0]
384 return inst.args[0]
384
385
385 class alias(object):
386 class alias(object):
386 """Parsed result of alias"""
387 """Parsed result of alias"""
387
388
388 def __init__(self, name, args, err, replacement):
389 def __init__(self, name, args, err, replacement):
389 self.name = name
390 self.name = name
390 self.args = args
391 self.args = args
391 self.error = err
392 self.error = err
392 self.replacement = replacement
393 self.replacement = replacement
393 # whether own `error` information is already shown or not.
394 # whether own `error` information is already shown or not.
394 # this avoids showing same warning multiple times at each
395 # this avoids showing same warning multiple times at each
395 # `expandaliases`.
396 # `expandaliases`.
396 self.warned = False
397 self.warned = False
397
398
398 class basealiasrules(object):
399 class basealiasrules(object):
399 """Parsing and expansion rule set of aliases
400 """Parsing and expansion rule set of aliases
400
401
401 This is a helper for fileset/revset/template aliases. A concrete rule set
402 This is a helper for fileset/revset/template aliases. A concrete rule set
402 should be made by sub-classing this and implementing class/static methods.
403 should be made by sub-classing this and implementing class/static methods.
403
404
404 It supports alias expansion of symbol and function-call styles::
405 It supports alias expansion of symbol and function-call styles::
405
406
406 # decl = defn
407 # decl = defn
407 h = heads(default)
408 h = heads(default)
408 b($1) = ancestors($1) - ancestors(default)
409 b($1) = ancestors($1) - ancestors(default)
409 """
410 """
410 # typically a config section, which will be included in error messages
411 # typically a config section, which will be included in error messages
411 _section = None
412 _section = None
412 # tag of symbol node
413 # tag of symbol node
413 _symbolnode = 'symbol'
414 _symbolnode = 'symbol'
414
415
415 def __new__(cls):
416 def __new__(cls):
416 raise TypeError("'%s' is not instantiatable" % cls.__name__)
417 raise TypeError("'%s' is not instantiatable" % cls.__name__)
417
418
418 @staticmethod
419 @staticmethod
419 def _parse(spec):
420 def _parse(spec):
420 """Parse an alias name, arguments and definition"""
421 """Parse an alias name, arguments and definition"""
421 raise NotImplementedError
422 raise NotImplementedError
422
423
423 @staticmethod
424 @staticmethod
424 def _trygetfunc(tree):
425 def _trygetfunc(tree):
425 """Return (name, args) if tree is a function; otherwise None"""
426 """Return (name, args) if tree is a function; otherwise None"""
426 raise NotImplementedError
427 raise NotImplementedError
427
428
428 @classmethod
429 @classmethod
429 def _builddecl(cls, decl):
430 def _builddecl(cls, decl):
430 """Parse an alias declaration into ``(name, args, errorstr)``
431 """Parse an alias declaration into ``(name, args, errorstr)``
431
432
432 This function analyzes the parsed tree. The parsing rule is provided
433 This function analyzes the parsed tree. The parsing rule is provided
433 by ``_parse()``.
434 by ``_parse()``.
434
435
435 - ``name``: of declared alias (may be ``decl`` itself at error)
436 - ``name``: of declared alias (may be ``decl`` itself at error)
436 - ``args``: list of argument names (or None for symbol declaration)
437 - ``args``: list of argument names (or None for symbol declaration)
437 - ``errorstr``: detail about detected error (or None)
438 - ``errorstr``: detail about detected error (or None)
438
439
439 >>> sym = lambda x: (b'symbol', x)
440 >>> sym = lambda x: (b'symbol', x)
440 >>> symlist = lambda *xs: (b'list',) + tuple(sym(x) for x in xs)
441 >>> symlist = lambda *xs: (b'list',) + tuple(sym(x) for x in xs)
441 >>> func = lambda n, a: (b'func', sym(n), a)
442 >>> func = lambda n, a: (b'func', sym(n), a)
442 >>> parsemap = {
443 >>> parsemap = {
443 ... b'foo': sym(b'foo'),
444 ... b'foo': sym(b'foo'),
444 ... b'$foo': sym(b'$foo'),
445 ... b'$foo': sym(b'$foo'),
445 ... b'foo::bar': (b'dagrange', sym(b'foo'), sym(b'bar')),
446 ... b'foo::bar': (b'dagrange', sym(b'foo'), sym(b'bar')),
446 ... b'foo()': func(b'foo', None),
447 ... b'foo()': func(b'foo', None),
447 ... b'$foo()': func(b'$foo', None),
448 ... b'$foo()': func(b'$foo', None),
448 ... b'foo($1, $2)': func(b'foo', symlist(b'$1', b'$2')),
449 ... b'foo($1, $2)': func(b'foo', symlist(b'$1', b'$2')),
449 ... b'foo(bar_bar, baz.baz)':
450 ... b'foo(bar_bar, baz.baz)':
450 ... func(b'foo', symlist(b'bar_bar', b'baz.baz')),
451 ... func(b'foo', symlist(b'bar_bar', b'baz.baz')),
451 ... b'foo(bar($1, $2))':
452 ... b'foo(bar($1, $2))':
452 ... func(b'foo', func(b'bar', symlist(b'$1', b'$2'))),
453 ... func(b'foo', func(b'bar', symlist(b'$1', b'$2'))),
453 ... b'foo($1, $2, nested($1, $2))':
454 ... b'foo($1, $2, nested($1, $2))':
454 ... func(b'foo', (symlist(b'$1', b'$2') +
455 ... func(b'foo', (symlist(b'$1', b'$2') +
455 ... (func(b'nested', symlist(b'$1', b'$2')),))),
456 ... (func(b'nested', symlist(b'$1', b'$2')),))),
456 ... b'foo("bar")': func(b'foo', (b'string', b'bar')),
457 ... b'foo("bar")': func(b'foo', (b'string', b'bar')),
457 ... b'foo($1, $2': error.ParseError(b'unexpected token: end', 10),
458 ... b'foo($1, $2': error.ParseError(b'unexpected token: end', 10),
458 ... b'foo("bar': error.ParseError(b'unterminated string', 5),
459 ... b'foo("bar': error.ParseError(b'unterminated string', 5),
459 ... b'foo($1, $2, $1)': func(b'foo', symlist(b'$1', b'$2', b'$1')),
460 ... b'foo($1, $2, $1)': func(b'foo', symlist(b'$1', b'$2', b'$1')),
460 ... }
461 ... }
461 >>> def parse(expr):
462 >>> def parse(expr):
462 ... x = parsemap[expr]
463 ... x = parsemap[expr]
463 ... if isinstance(x, Exception):
464 ... if isinstance(x, Exception):
464 ... raise x
465 ... raise x
465 ... return x
466 ... return x
466 >>> def trygetfunc(tree):
467 >>> def trygetfunc(tree):
467 ... if not tree or tree[0] != b'func' or tree[1][0] != b'symbol':
468 ... if not tree or tree[0] != b'func' or tree[1][0] != b'symbol':
468 ... return None
469 ... return None
469 ... if not tree[2]:
470 ... if not tree[2]:
470 ... return tree[1][1], []
471 ... return tree[1][1], []
471 ... if tree[2][0] == b'list':
472 ... if tree[2][0] == b'list':
472 ... return tree[1][1], list(tree[2][1:])
473 ... return tree[1][1], list(tree[2][1:])
473 ... return tree[1][1], [tree[2]]
474 ... return tree[1][1], [tree[2]]
474 >>> class aliasrules(basealiasrules):
475 >>> class aliasrules(basealiasrules):
475 ... _parse = staticmethod(parse)
476 ... _parse = staticmethod(parse)
476 ... _trygetfunc = staticmethod(trygetfunc)
477 ... _trygetfunc = staticmethod(trygetfunc)
477 >>> builddecl = aliasrules._builddecl
478 >>> builddecl = aliasrules._builddecl
478 >>> builddecl(b'foo')
479 >>> builddecl(b'foo')
479 ('foo', None, None)
480 ('foo', None, None)
480 >>> builddecl(b'$foo')
481 >>> builddecl(b'$foo')
481 ('$foo', None, "invalid symbol '$foo'")
482 ('$foo', None, "invalid symbol '$foo'")
482 >>> builddecl(b'foo::bar')
483 >>> builddecl(b'foo::bar')
483 ('foo::bar', None, 'invalid format')
484 ('foo::bar', None, 'invalid format')
484 >>> builddecl(b'foo()')
485 >>> builddecl(b'foo()')
485 ('foo', [], None)
486 ('foo', [], None)
486 >>> builddecl(b'$foo()')
487 >>> builddecl(b'$foo()')
487 ('$foo()', None, "invalid function '$foo'")
488 ('$foo()', None, "invalid function '$foo'")
488 >>> builddecl(b'foo($1, $2)')
489 >>> builddecl(b'foo($1, $2)')
489 ('foo', ['$1', '$2'], None)
490 ('foo', ['$1', '$2'], None)
490 >>> builddecl(b'foo(bar_bar, baz.baz)')
491 >>> builddecl(b'foo(bar_bar, baz.baz)')
491 ('foo', ['bar_bar', 'baz.baz'], None)
492 ('foo', ['bar_bar', 'baz.baz'], None)
492 >>> builddecl(b'foo($1, $2, nested($1, $2))')
493 >>> builddecl(b'foo($1, $2, nested($1, $2))')
493 ('foo($1, $2, nested($1, $2))', None, 'invalid argument list')
494 ('foo($1, $2, nested($1, $2))', None, 'invalid argument list')
494 >>> builddecl(b'foo(bar($1, $2))')
495 >>> builddecl(b'foo(bar($1, $2))')
495 ('foo(bar($1, $2))', None, 'invalid argument list')
496 ('foo(bar($1, $2))', None, 'invalid argument list')
496 >>> builddecl(b'foo("bar")')
497 >>> builddecl(b'foo("bar")')
497 ('foo("bar")', None, 'invalid argument list')
498 ('foo("bar")', None, 'invalid argument list')
498 >>> builddecl(b'foo($1, $2')
499 >>> builddecl(b'foo($1, $2')
499 ('foo($1, $2', None, 'at 10: unexpected token: end')
500 ('foo($1, $2', None, 'at 10: unexpected token: end')
500 >>> builddecl(b'foo("bar')
501 >>> builddecl(b'foo("bar')
501 ('foo("bar', None, 'at 5: unterminated string')
502 ('foo("bar', None, 'at 5: unterminated string')
502 >>> builddecl(b'foo($1, $2, $1)')
503 >>> builddecl(b'foo($1, $2, $1)')
503 ('foo', None, 'argument names collide with each other')
504 ('foo', None, 'argument names collide with each other')
504 """
505 """
505 try:
506 try:
506 tree = cls._parse(decl)
507 tree = cls._parse(decl)
507 except error.ParseError as inst:
508 except error.ParseError as inst:
508 return (decl, None, parseerrordetail(inst))
509 return (decl, None, parseerrordetail(inst))
509
510
510 if tree[0] == cls._symbolnode:
511 if tree[0] == cls._symbolnode:
511 # "name = ...." style
512 # "name = ...." style
512 name = tree[1]
513 name = tree[1]
513 if name.startswith('$'):
514 if name.startswith('$'):
514 return (decl, None, _("invalid symbol '%s'") % name)
515 return (decl, None, _("invalid symbol '%s'") % name)
515 return (name, None, None)
516 return (name, None, None)
516
517
517 func = cls._trygetfunc(tree)
518 func = cls._trygetfunc(tree)
518 if func:
519 if func:
519 # "name(arg, ....) = ...." style
520 # "name(arg, ....) = ...." style
520 name, args = func
521 name, args = func
521 if name.startswith('$'):
522 if name.startswith('$'):
522 return (decl, None, _("invalid function '%s'") % name)
523 return (decl, None, _("invalid function '%s'") % name)
523 if any(t[0] != cls._symbolnode for t in args):
524 if any(t[0] != cls._symbolnode for t in args):
524 return (decl, None, _("invalid argument list"))
525 return (decl, None, _("invalid argument list"))
525 if len(args) != len(set(args)):
526 if len(args) != len(set(args)):
526 return (name, None, _("argument names collide with each other"))
527 return (name, None, _("argument names collide with each other"))
527 return (name, [t[1] for t in args], None)
528 return (name, [t[1] for t in args], None)
528
529
529 return (decl, None, _("invalid format"))
530 return (decl, None, _("invalid format"))
530
531
531 @classmethod
532 @classmethod
532 def _relabelargs(cls, tree, args):
533 def _relabelargs(cls, tree, args):
533 """Mark alias arguments as ``_aliasarg``"""
534 """Mark alias arguments as ``_aliasarg``"""
534 if not isinstance(tree, tuple):
535 if not isinstance(tree, tuple):
535 return tree
536 return tree
536 op = tree[0]
537 op = tree[0]
537 if op != cls._symbolnode:
538 if op != cls._symbolnode:
538 return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:])
539 return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:])
539
540
540 assert len(tree) == 2
541 assert len(tree) == 2
541 sym = tree[1]
542 sym = tree[1]
542 if sym in args:
543 if sym in args:
543 op = '_aliasarg'
544 op = '_aliasarg'
544 elif sym.startswith('$'):
545 elif sym.startswith('$'):
545 raise error.ParseError(_("invalid symbol '%s'") % sym)
546 raise error.ParseError(_("invalid symbol '%s'") % sym)
546 return (op, sym)
547 return (op, sym)
547
548
548 @classmethod
549 @classmethod
549 def _builddefn(cls, defn, args):
550 def _builddefn(cls, defn, args):
550 """Parse an alias definition into a tree and marks substitutions
551 """Parse an alias definition into a tree and marks substitutions
551
552
552 This function marks alias argument references as ``_aliasarg``. The
553 This function marks alias argument references as ``_aliasarg``. The
553 parsing rule is provided by ``_parse()``.
554 parsing rule is provided by ``_parse()``.
554
555
555 ``args`` is a list of alias argument names, or None if the alias
556 ``args`` is a list of alias argument names, or None if the alias
556 is declared as a symbol.
557 is declared as a symbol.
557
558
558 >>> from . import pycompat
559 >>> from . import pycompat
559 >>> parsemap = {
560 >>> parsemap = {
560 ... b'$1 or foo': (b'or', (b'symbol', b'$1'), (b'symbol', b'foo')),
561 ... b'$1 or foo': (b'or', (b'symbol', b'$1'), (b'symbol', b'foo')),
561 ... b'$1 or $bar':
562 ... b'$1 or $bar':
562 ... (b'or', (b'symbol', b'$1'), (b'symbol', b'$bar')),
563 ... (b'or', (b'symbol', b'$1'), (b'symbol', b'$bar')),
563 ... b'$10 or baz':
564 ... b'$10 or baz':
564 ... (b'or', (b'symbol', b'$10'), (b'symbol', b'baz')),
565 ... (b'or', (b'symbol', b'$10'), (b'symbol', b'baz')),
565 ... b'"$1" or "foo"':
566 ... b'"$1" or "foo"':
566 ... (b'or', (b'string', b'$1'), (b'string', b'foo')),
567 ... (b'or', (b'string', b'$1'), (b'string', b'foo')),
567 ... }
568 ... }
568 >>> class aliasrules(basealiasrules):
569 >>> class aliasrules(basealiasrules):
569 ... _parse = staticmethod(parsemap.__getitem__)
570 ... _parse = staticmethod(parsemap.__getitem__)
570 ... _trygetfunc = staticmethod(lambda x: None)
571 ... _trygetfunc = staticmethod(lambda x: None)
571 >>> builddefn = aliasrules._builddefn
572 >>> builddefn = aliasrules._builddefn
572 >>> def pprint(tree):
573 >>> def pprint(tree):
573 ... s = prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
574 ... s = prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
574 ... print(pycompat.sysstr(s))
575 ... print(pycompat.sysstr(s))
575 >>> args = [b'$1', b'$2', b'foo']
576 >>> args = [b'$1', b'$2', b'foo']
576 >>> pprint(builddefn(b'$1 or foo', args))
577 >>> pprint(builddefn(b'$1 or foo', args))
577 (or
578 (or
578 (_aliasarg '$1')
579 (_aliasarg '$1')
579 (_aliasarg 'foo'))
580 (_aliasarg 'foo'))
580 >>> try:
581 >>> try:
581 ... builddefn(b'$1 or $bar', args)
582 ... builddefn(b'$1 or $bar', args)
582 ... except error.ParseError as inst:
583 ... except error.ParseError as inst:
583 ... print(pycompat.sysstr(parseerrordetail(inst)))
584 ... print(pycompat.sysstr(parseerrordetail(inst)))
584 invalid symbol '$bar'
585 invalid symbol '$bar'
585 >>> args = [b'$1', b'$10', b'foo']
586 >>> args = [b'$1', b'$10', b'foo']
586 >>> pprint(builddefn(b'$10 or baz', args))
587 >>> pprint(builddefn(b'$10 or baz', args))
587 (or
588 (or
588 (_aliasarg '$10')
589 (_aliasarg '$10')
589 (symbol 'baz'))
590 (symbol 'baz'))
590 >>> pprint(builddefn(b'"$1" or "foo"', args))
591 >>> pprint(builddefn(b'"$1" or "foo"', args))
591 (or
592 (or
592 (string '$1')
593 (string '$1')
593 (string 'foo'))
594 (string 'foo'))
594 """
595 """
595 tree = cls._parse(defn)
596 tree = cls._parse(defn)
596 if args:
597 if args:
597 args = set(args)
598 args = set(args)
598 else:
599 else:
599 args = set()
600 args = set()
600 return cls._relabelargs(tree, args)
601 return cls._relabelargs(tree, args)
601
602
602 @classmethod
603 @classmethod
603 def build(cls, decl, defn):
604 def build(cls, decl, defn):
604 """Parse an alias declaration and definition into an alias object"""
605 """Parse an alias declaration and definition into an alias object"""
605 repl = efmt = None
606 repl = efmt = None
606 name, args, err = cls._builddecl(decl)
607 name, args, err = cls._builddecl(decl)
607 if err:
608 if err:
608 efmt = _('bad declaration of %(section)s "%(name)s": %(error)s')
609 efmt = _('bad declaration of %(section)s "%(name)s": %(error)s')
609 else:
610 else:
610 try:
611 try:
611 repl = cls._builddefn(defn, args)
612 repl = cls._builddefn(defn, args)
612 except error.ParseError as inst:
613 except error.ParseError as inst:
613 err = parseerrordetail(inst)
614 err = parseerrordetail(inst)
614 efmt = _('bad definition of %(section)s "%(name)s": %(error)s')
615 efmt = _('bad definition of %(section)s "%(name)s": %(error)s')
615 if err:
616 if err:
616 err = efmt % {'section': cls._section, 'name': name, 'error': err}
617 err = efmt % {'section': cls._section, 'name': name, 'error': err}
617 return alias(name, args, err, repl)
618 return alias(name, args, err, repl)
618
619
619 @classmethod
620 @classmethod
620 def buildmap(cls, items):
621 def buildmap(cls, items):
621 """Parse a list of alias (name, replacement) pairs into a dict of
622 """Parse a list of alias (name, replacement) pairs into a dict of
622 alias objects"""
623 alias objects"""
623 aliases = {}
624 aliases = {}
624 for decl, defn in items:
625 for decl, defn in items:
625 a = cls.build(decl, defn)
626 a = cls.build(decl, defn)
626 aliases[a.name] = a
627 aliases[a.name] = a
627 return aliases
628 return aliases
628
629
629 @classmethod
630 @classmethod
630 def _getalias(cls, aliases, tree):
631 def _getalias(cls, aliases, tree):
631 """If tree looks like an unexpanded alias, return (alias, pattern-args)
632 """If tree looks like an unexpanded alias, return (alias, pattern-args)
632 pair. Return None otherwise.
633 pair. Return None otherwise.
633 """
634 """
634 if not isinstance(tree, tuple):
635 if not isinstance(tree, tuple):
635 return None
636 return None
636 if tree[0] == cls._symbolnode:
637 if tree[0] == cls._symbolnode:
637 name = tree[1]
638 name = tree[1]
638 a = aliases.get(name)
639 a = aliases.get(name)
639 if a and a.args is None:
640 if a and a.args is None:
640 return a, None
641 return a, None
641 func = cls._trygetfunc(tree)
642 func = cls._trygetfunc(tree)
642 if func:
643 if func:
643 name, args = func
644 name, args = func
644 a = aliases.get(name)
645 a = aliases.get(name)
645 if a and a.args is not None:
646 if a and a.args is not None:
646 return a, args
647 return a, args
647 return None
648 return None
648
649
649 @classmethod
650 @classmethod
650 def _expandargs(cls, tree, args):
651 def _expandargs(cls, tree, args):
651 """Replace _aliasarg instances with the substitution value of the
652 """Replace _aliasarg instances with the substitution value of the
652 same name in args, recursively.
653 same name in args, recursively.
653 """
654 """
654 if not isinstance(tree, tuple):
655 if not isinstance(tree, tuple):
655 return tree
656 return tree
656 if tree[0] == '_aliasarg':
657 if tree[0] == '_aliasarg':
657 sym = tree[1]
658 sym = tree[1]
658 return args[sym]
659 return args[sym]
659 return tuple(cls._expandargs(t, args) for t in tree)
660 return tuple(cls._expandargs(t, args) for t in tree)
660
661
661 @classmethod
662 @classmethod
662 def _expand(cls, aliases, tree, expanding, cache):
663 def _expand(cls, aliases, tree, expanding, cache):
663 if not isinstance(tree, tuple):
664 if not isinstance(tree, tuple):
664 return tree
665 return tree
665 r = cls._getalias(aliases, tree)
666 r = cls._getalias(aliases, tree)
666 if r is None:
667 if r is None:
667 return tuple(cls._expand(aliases, t, expanding, cache)
668 return tuple(cls._expand(aliases, t, expanding, cache)
668 for t in tree)
669 for t in tree)
669 a, l = r
670 a, l = r
670 if a.error:
671 if a.error:
671 raise error.Abort(a.error)
672 raise error.Abort(a.error)
672 if a in expanding:
673 if a in expanding:
673 raise error.ParseError(_('infinite expansion of %(section)s '
674 raise error.ParseError(_('infinite expansion of %(section)s '
674 '"%(name)s" detected')
675 '"%(name)s" detected')
675 % {'section': cls._section, 'name': a.name})
676 % {'section': cls._section, 'name': a.name})
676 # get cacheable replacement tree by expanding aliases recursively
677 # get cacheable replacement tree by expanding aliases recursively
677 expanding.append(a)
678 expanding.append(a)
678 if a.name not in cache:
679 if a.name not in cache:
679 cache[a.name] = cls._expand(aliases, a.replacement, expanding,
680 cache[a.name] = cls._expand(aliases, a.replacement, expanding,
680 cache)
681 cache)
681 result = cache[a.name]
682 result = cache[a.name]
682 expanding.pop()
683 expanding.pop()
683 if a.args is None:
684 if a.args is None:
684 return result
685 return result
685 # substitute function arguments in replacement tree
686 # substitute function arguments in replacement tree
686 if len(l) != len(a.args):
687 if len(l) != len(a.args):
687 raise error.ParseError(_('invalid number of arguments: %d')
688 raise error.ParseError(_('invalid number of arguments: %d')
688 % len(l))
689 % len(l))
689 l = [cls._expand(aliases, t, [], cache) for t in l]
690 l = [cls._expand(aliases, t, [], cache) for t in l]
690 return cls._expandargs(result, dict(zip(a.args, l)))
691 return cls._expandargs(result, dict(zip(a.args, l)))
691
692
692 @classmethod
693 @classmethod
693 def expand(cls, aliases, tree):
694 def expand(cls, aliases, tree):
694 """Expand aliases in tree, recursively.
695 """Expand aliases in tree, recursively.
695
696
696 'aliases' is a dictionary mapping user defined aliases to alias objects.
697 'aliases' is a dictionary mapping user defined aliases to alias objects.
697 """
698 """
698 return cls._expand(aliases, tree, [], {})
699 return cls._expand(aliases, tree, [], {})
@@ -1,880 +1,880 b''
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import ssl
15 import ssl
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 error,
19 error,
20 node,
20 node,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24 from .utils import (
24 from .utils import (
25 procutil,
25 procutil,
26 stringutil,
26 stringutil,
27 )
27 )
28
28
29 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
29 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
30 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
30 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
31 # all exposed via the "ssl" module.
31 # all exposed via the "ssl" module.
32 #
32 #
33 # Depending on the version of Python being used, SSL/TLS support is either
33 # Depending on the version of Python being used, SSL/TLS support is either
34 # modern/secure or legacy/insecure. Many operations in this module have
34 # modern/secure or legacy/insecure. Many operations in this module have
35 # separate code paths depending on support in Python.
35 # separate code paths depending on support in Python.
36
36
37 configprotocols = {
37 configprotocols = {
38 'tls1.0',
38 'tls1.0',
39 'tls1.1',
39 'tls1.1',
40 'tls1.2',
40 'tls1.2',
41 }
41 }
42
42
43 hassni = getattr(ssl, 'HAS_SNI', False)
43 hassni = getattr(ssl, 'HAS_SNI', False)
44
44
45 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
45 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
46 # against doesn't support them.
46 # against doesn't support them.
47 supportedprotocols = {'tls1.0'}
47 supportedprotocols = {'tls1.0'}
48 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
48 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
49 supportedprotocols.add('tls1.1')
49 supportedprotocols.add('tls1.1')
50 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
50 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
51 supportedprotocols.add('tls1.2')
51 supportedprotocols.add('tls1.2')
52
52
53 try:
53 try:
54 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
54 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
55 # SSL/TLS features are available.
55 # SSL/TLS features are available.
56 SSLContext = ssl.SSLContext
56 SSLContext = ssl.SSLContext
57 modernssl = True
57 modernssl = True
58 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
58 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
59 except AttributeError:
59 except AttributeError:
60 modernssl = False
60 modernssl = False
61 _canloaddefaultcerts = False
61 _canloaddefaultcerts = False
62
62
63 # We implement SSLContext using the interface from the standard library.
63 # We implement SSLContext using the interface from the standard library.
64 class SSLContext(object):
64 class SSLContext(object):
65 def __init__(self, protocol):
65 def __init__(self, protocol):
66 # From the public interface of SSLContext
66 # From the public interface of SSLContext
67 self.protocol = protocol
67 self.protocol = protocol
68 self.check_hostname = False
68 self.check_hostname = False
69 self.options = 0
69 self.options = 0
70 self.verify_mode = ssl.CERT_NONE
70 self.verify_mode = ssl.CERT_NONE
71
71
72 # Used by our implementation.
72 # Used by our implementation.
73 self._certfile = None
73 self._certfile = None
74 self._keyfile = None
74 self._keyfile = None
75 self._certpassword = None
75 self._certpassword = None
76 self._cacerts = None
76 self._cacerts = None
77 self._ciphers = None
77 self._ciphers = None
78
78
79 def load_cert_chain(self, certfile, keyfile=None, password=None):
79 def load_cert_chain(self, certfile, keyfile=None, password=None):
80 self._certfile = certfile
80 self._certfile = certfile
81 self._keyfile = keyfile
81 self._keyfile = keyfile
82 self._certpassword = password
82 self._certpassword = password
83
83
84 def load_default_certs(self, purpose=None):
84 def load_default_certs(self, purpose=None):
85 pass
85 pass
86
86
87 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
87 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
88 if capath:
88 if capath:
89 raise error.Abort(_('capath not supported'))
89 raise error.Abort(_('capath not supported'))
90 if cadata:
90 if cadata:
91 raise error.Abort(_('cadata not supported'))
91 raise error.Abort(_('cadata not supported'))
92
92
93 self._cacerts = cafile
93 self._cacerts = cafile
94
94
95 def set_ciphers(self, ciphers):
95 def set_ciphers(self, ciphers):
96 self._ciphers = ciphers
96 self._ciphers = ciphers
97
97
98 def wrap_socket(self, socket, server_hostname=None, server_side=False):
98 def wrap_socket(self, socket, server_hostname=None, server_side=False):
99 # server_hostname is unique to SSLContext.wrap_socket and is used
99 # server_hostname is unique to SSLContext.wrap_socket and is used
100 # for SNI in that context. So there's nothing for us to do with it
100 # for SNI in that context. So there's nothing for us to do with it
101 # in this legacy code since we don't support SNI.
101 # in this legacy code since we don't support SNI.
102
102
103 args = {
103 args = {
104 r'keyfile': self._keyfile,
104 r'keyfile': self._keyfile,
105 r'certfile': self._certfile,
105 r'certfile': self._certfile,
106 r'server_side': server_side,
106 r'server_side': server_side,
107 r'cert_reqs': self.verify_mode,
107 r'cert_reqs': self.verify_mode,
108 r'ssl_version': self.protocol,
108 r'ssl_version': self.protocol,
109 r'ca_certs': self._cacerts,
109 r'ca_certs': self._cacerts,
110 r'ciphers': self._ciphers,
110 r'ciphers': self._ciphers,
111 }
111 }
112
112
113 return ssl.wrap_socket(socket, **args)
113 return ssl.wrap_socket(socket, **args)
114
114
115 def _hostsettings(ui, hostname):
115 def _hostsettings(ui, hostname):
116 """Obtain security settings for a hostname.
116 """Obtain security settings for a hostname.
117
117
118 Returns a dict of settings relevant to that hostname.
118 Returns a dict of settings relevant to that hostname.
119 """
119 """
120 bhostname = pycompat.bytesurl(hostname)
120 bhostname = pycompat.bytesurl(hostname)
121 s = {
121 s = {
122 # Whether we should attempt to load default/available CA certs
122 # Whether we should attempt to load default/available CA certs
123 # if an explicit ``cafile`` is not defined.
123 # if an explicit ``cafile`` is not defined.
124 'allowloaddefaultcerts': True,
124 'allowloaddefaultcerts': True,
125 # List of 2-tuple of (hash algorithm, hash).
125 # List of 2-tuple of (hash algorithm, hash).
126 'certfingerprints': [],
126 'certfingerprints': [],
127 # Path to file containing concatenated CA certs. Used by
127 # Path to file containing concatenated CA certs. Used by
128 # SSLContext.load_verify_locations().
128 # SSLContext.load_verify_locations().
129 'cafile': None,
129 'cafile': None,
130 # Whether certificate verification should be disabled.
130 # Whether certificate verification should be disabled.
131 'disablecertverification': False,
131 'disablecertverification': False,
132 # Whether the legacy [hostfingerprints] section has data for this host.
132 # Whether the legacy [hostfingerprints] section has data for this host.
133 'legacyfingerprint': False,
133 'legacyfingerprint': False,
134 # PROTOCOL_* constant to use for SSLContext.__init__.
134 # PROTOCOL_* constant to use for SSLContext.__init__.
135 'protocol': None,
135 'protocol': None,
136 # String representation of minimum protocol to be used for UI
136 # String representation of minimum protocol to be used for UI
137 # presentation.
137 # presentation.
138 'protocolui': None,
138 'protocolui': None,
139 # ssl.CERT_* constant used by SSLContext.verify_mode.
139 # ssl.CERT_* constant used by SSLContext.verify_mode.
140 'verifymode': None,
140 'verifymode': None,
141 # Defines extra ssl.OP* bitwise options to set.
141 # Defines extra ssl.OP* bitwise options to set.
142 'ctxoptions': None,
142 'ctxoptions': None,
143 # OpenSSL Cipher List to use (instead of default).
143 # OpenSSL Cipher List to use (instead of default).
144 'ciphers': None,
144 'ciphers': None,
145 }
145 }
146
146
147 # Allow minimum TLS protocol to be specified in the config.
147 # Allow minimum TLS protocol to be specified in the config.
148 def validateprotocol(protocol, key):
148 def validateprotocol(protocol, key):
149 if protocol not in configprotocols:
149 if protocol not in configprotocols:
150 raise error.Abort(
150 raise error.Abort(
151 _('unsupported protocol from hostsecurity.%s: %s') %
151 _('unsupported protocol from hostsecurity.%s: %s') %
152 (key, protocol),
152 (key, protocol),
153 hint=_('valid protocols: %s') %
153 hint=_('valid protocols: %s') %
154 ' '.join(sorted(configprotocols)))
154 ' '.join(sorted(configprotocols)))
155
155
156 # We default to TLS 1.1+ where we can because TLS 1.0 has known
156 # We default to TLS 1.1+ where we can because TLS 1.0 has known
157 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
157 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
158 # TLS 1.0+ via config options in case a legacy server is encountered.
158 # TLS 1.0+ via config options in case a legacy server is encountered.
159 if 'tls1.1' in supportedprotocols:
159 if 'tls1.1' in supportedprotocols:
160 defaultprotocol = 'tls1.1'
160 defaultprotocol = 'tls1.1'
161 else:
161 else:
162 # Let people know they are borderline secure.
162 # Let people know they are borderline secure.
163 # We don't document this config option because we want people to see
163 # We don't document this config option because we want people to see
164 # the bold warnings on the web site.
164 # the bold warnings on the web site.
165 # internal config: hostsecurity.disabletls10warning
165 # internal config: hostsecurity.disabletls10warning
166 if not ui.configbool('hostsecurity', 'disabletls10warning'):
166 if not ui.configbool('hostsecurity', 'disabletls10warning'):
167 ui.warn(_('warning: connecting to %s using legacy security '
167 ui.warn(_('warning: connecting to %s using legacy security '
168 'technology (TLS 1.0); see '
168 'technology (TLS 1.0); see '
169 'https://mercurial-scm.org/wiki/SecureConnections for '
169 'https://mercurial-scm.org/wiki/SecureConnections for '
170 'more info\n') % bhostname)
170 'more info\n') % bhostname)
171 defaultprotocol = 'tls1.0'
171 defaultprotocol = 'tls1.0'
172
172
173 key = 'minimumprotocol'
173 key = 'minimumprotocol'
174 protocol = ui.config('hostsecurity', key, defaultprotocol)
174 protocol = ui.config('hostsecurity', key, defaultprotocol)
175 validateprotocol(protocol, key)
175 validateprotocol(protocol, key)
176
176
177 key = '%s:minimumprotocol' % bhostname
177 key = '%s:minimumprotocol' % bhostname
178 protocol = ui.config('hostsecurity', key, protocol)
178 protocol = ui.config('hostsecurity', key, protocol)
179 validateprotocol(protocol, key)
179 validateprotocol(protocol, key)
180
180
181 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
181 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
182 # We always print a "connection security to %s is disabled..." message when
182 # We always print a "connection security to %s is disabled..." message when
183 # --insecure is used. So no need to print anything more here.
183 # --insecure is used. So no need to print anything more here.
184 if ui.insecureconnections:
184 if ui.insecureconnections:
185 protocol = 'tls1.0'
185 protocol = 'tls1.0'
186
186
187 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
187 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
188
188
189 ciphers = ui.config('hostsecurity', 'ciphers')
189 ciphers = ui.config('hostsecurity', 'ciphers')
190 ciphers = ui.config('hostsecurity', '%s:ciphers' % bhostname, ciphers)
190 ciphers = ui.config('hostsecurity', '%s:ciphers' % bhostname, ciphers)
191 s['ciphers'] = ciphers
191 s['ciphers'] = ciphers
192
192
193 # Look for fingerprints in [hostsecurity] section. Value is a list
193 # Look for fingerprints in [hostsecurity] section. Value is a list
194 # of <alg>:<fingerprint> strings.
194 # of <alg>:<fingerprint> strings.
195 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % bhostname)
195 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % bhostname)
196 for fingerprint in fingerprints:
196 for fingerprint in fingerprints:
197 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
197 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
198 raise error.Abort(_('invalid fingerprint for %s: %s') % (
198 raise error.Abort(_('invalid fingerprint for %s: %s') % (
199 bhostname, fingerprint),
199 bhostname, fingerprint),
200 hint=_('must begin with "sha1:", "sha256:", '
200 hint=_('must begin with "sha1:", "sha256:", '
201 'or "sha512:"'))
201 'or "sha512:"'))
202
202
203 alg, fingerprint = fingerprint.split(':', 1)
203 alg, fingerprint = fingerprint.split(':', 1)
204 fingerprint = fingerprint.replace(':', '').lower()
204 fingerprint = fingerprint.replace(':', '').lower()
205 s['certfingerprints'].append((alg, fingerprint))
205 s['certfingerprints'].append((alg, fingerprint))
206
206
207 # Fingerprints from [hostfingerprints] are always SHA-1.
207 # Fingerprints from [hostfingerprints] are always SHA-1.
208 for fingerprint in ui.configlist('hostfingerprints', bhostname):
208 for fingerprint in ui.configlist('hostfingerprints', bhostname):
209 fingerprint = fingerprint.replace(':', '').lower()
209 fingerprint = fingerprint.replace(':', '').lower()
210 s['certfingerprints'].append(('sha1', fingerprint))
210 s['certfingerprints'].append(('sha1', fingerprint))
211 s['legacyfingerprint'] = True
211 s['legacyfingerprint'] = True
212
212
213 # If a host cert fingerprint is defined, it is the only thing that
213 # If a host cert fingerprint is defined, it is the only thing that
214 # matters. No need to validate CA certs.
214 # matters. No need to validate CA certs.
215 if s['certfingerprints']:
215 if s['certfingerprints']:
216 s['verifymode'] = ssl.CERT_NONE
216 s['verifymode'] = ssl.CERT_NONE
217 s['allowloaddefaultcerts'] = False
217 s['allowloaddefaultcerts'] = False
218
218
219 # If --insecure is used, don't take CAs into consideration.
219 # If --insecure is used, don't take CAs into consideration.
220 elif ui.insecureconnections:
220 elif ui.insecureconnections:
221 s['disablecertverification'] = True
221 s['disablecertverification'] = True
222 s['verifymode'] = ssl.CERT_NONE
222 s['verifymode'] = ssl.CERT_NONE
223 s['allowloaddefaultcerts'] = False
223 s['allowloaddefaultcerts'] = False
224
224
225 if ui.configbool('devel', 'disableloaddefaultcerts'):
225 if ui.configbool('devel', 'disableloaddefaultcerts'):
226 s['allowloaddefaultcerts'] = False
226 s['allowloaddefaultcerts'] = False
227
227
228 # If both fingerprints and a per-host ca file are specified, issue a warning
228 # If both fingerprints and a per-host ca file are specified, issue a warning
229 # because users should not be surprised about what security is or isn't
229 # because users should not be surprised about what security is or isn't
230 # being performed.
230 # being performed.
231 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % bhostname)
231 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % bhostname)
232 if s['certfingerprints'] and cafile:
232 if s['certfingerprints'] and cafile:
233 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
233 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
234 'fingerprints defined; using host fingerprints for '
234 'fingerprints defined; using host fingerprints for '
235 'verification)\n') % bhostname)
235 'verification)\n') % bhostname)
236
236
237 # Try to hook up CA certificate validation unless something above
237 # Try to hook up CA certificate validation unless something above
238 # makes it not necessary.
238 # makes it not necessary.
239 if s['verifymode'] is None:
239 if s['verifymode'] is None:
240 # Look at per-host ca file first.
240 # Look at per-host ca file first.
241 if cafile:
241 if cafile:
242 cafile = util.expandpath(cafile)
242 cafile = util.expandpath(cafile)
243 if not os.path.exists(cafile):
243 if not os.path.exists(cafile):
244 raise error.Abort(_('path specified by %s does not exist: %s') %
244 raise error.Abort(_('path specified by %s does not exist: %s') %
245 ('hostsecurity.%s:verifycertsfile' % (
245 ('hostsecurity.%s:verifycertsfile' % (
246 bhostname,), cafile))
246 bhostname,), cafile))
247 s['cafile'] = cafile
247 s['cafile'] = cafile
248 else:
248 else:
249 # Find global certificates file in config.
249 # Find global certificates file in config.
250 cafile = ui.config('web', 'cacerts')
250 cafile = ui.config('web', 'cacerts')
251
251
252 if cafile:
252 if cafile:
253 cafile = util.expandpath(cafile)
253 cafile = util.expandpath(cafile)
254 if not os.path.exists(cafile):
254 if not os.path.exists(cafile):
255 raise error.Abort(_('could not find web.cacerts: %s') %
255 raise error.Abort(_('could not find web.cacerts: %s') %
256 cafile)
256 cafile)
257 elif s['allowloaddefaultcerts']:
257 elif s['allowloaddefaultcerts']:
258 # CAs not defined in config. Try to find system bundles.
258 # CAs not defined in config. Try to find system bundles.
259 cafile = _defaultcacerts(ui)
259 cafile = _defaultcacerts(ui)
260 if cafile:
260 if cafile:
261 ui.debug('using %s for CA file\n' % cafile)
261 ui.debug('using %s for CA file\n' % cafile)
262
262
263 s['cafile'] = cafile
263 s['cafile'] = cafile
264
264
265 # Require certificate validation if CA certs are being loaded and
265 # Require certificate validation if CA certs are being loaded and
266 # verification hasn't been disabled above.
266 # verification hasn't been disabled above.
267 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
267 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
268 s['verifymode'] = ssl.CERT_REQUIRED
268 s['verifymode'] = ssl.CERT_REQUIRED
269 else:
269 else:
270 # At this point we don't have a fingerprint, aren't being
270 # At this point we don't have a fingerprint, aren't being
271 # explicitly insecure, and can't load CA certs. Connecting
271 # explicitly insecure, and can't load CA certs. Connecting
272 # is insecure. We allow the connection and abort during
272 # is insecure. We allow the connection and abort during
273 # validation (once we have the fingerprint to print to the
273 # validation (once we have the fingerprint to print to the
274 # user).
274 # user).
275 s['verifymode'] = ssl.CERT_NONE
275 s['verifymode'] = ssl.CERT_NONE
276
276
277 assert s['protocol'] is not None
277 assert s['protocol'] is not None
278 assert s['ctxoptions'] is not None
278 assert s['ctxoptions'] is not None
279 assert s['verifymode'] is not None
279 assert s['verifymode'] is not None
280
280
281 return s
281 return s
282
282
283 def protocolsettings(protocol):
283 def protocolsettings(protocol):
284 """Resolve the protocol for a config value.
284 """Resolve the protocol for a config value.
285
285
286 Returns a 3-tuple of (protocol, options, ui value) where the first
286 Returns a 3-tuple of (protocol, options, ui value) where the first
287 2 items are values used by SSLContext and the last is a string value
287 2 items are values used by SSLContext and the last is a string value
288 of the ``minimumprotocol`` config option equivalent.
288 of the ``minimumprotocol`` config option equivalent.
289 """
289 """
290 if protocol not in configprotocols:
290 if protocol not in configprotocols:
291 raise ValueError('protocol value not supported: %s' % protocol)
291 raise ValueError('protocol value not supported: %s' % protocol)
292
292
293 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
293 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
294 # that both ends support, including TLS protocols. On legacy stacks,
294 # that both ends support, including TLS protocols. On legacy stacks,
295 # the highest it likely goes is TLS 1.0. On modern stacks, it can
295 # the highest it likely goes is TLS 1.0. On modern stacks, it can
296 # support TLS 1.2.
296 # support TLS 1.2.
297 #
297 #
298 # The PROTOCOL_TLSv* constants select a specific TLS version
298 # The PROTOCOL_TLSv* constants select a specific TLS version
299 # only (as opposed to multiple versions). So the method for
299 # only (as opposed to multiple versions). So the method for
300 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
300 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
301 # disable protocols via SSLContext.options and OP_NO_* constants.
301 # disable protocols via SSLContext.options and OP_NO_* constants.
302 # However, SSLContext.options doesn't work unless we have the
302 # However, SSLContext.options doesn't work unless we have the
303 # full/real SSLContext available to us.
303 # full/real SSLContext available to us.
304 if supportedprotocols == {'tls1.0'}:
304 if supportedprotocols == {'tls1.0'}:
305 if protocol != 'tls1.0':
305 if protocol != 'tls1.0':
306 raise error.Abort(_('current Python does not support protocol '
306 raise error.Abort(_('current Python does not support protocol '
307 'setting %s') % protocol,
307 'setting %s') % protocol,
308 hint=_('upgrade Python or disable setting since '
308 hint=_('upgrade Python or disable setting since '
309 'only TLS 1.0 is supported'))
309 'only TLS 1.0 is supported'))
310
310
311 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
311 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
312
312
313 # WARNING: returned options don't work unless the modern ssl module
313 # WARNING: returned options don't work unless the modern ssl module
314 # is available. Be careful when adding options here.
314 # is available. Be careful when adding options here.
315
315
316 # SSLv2 and SSLv3 are broken. We ban them outright.
316 # SSLv2 and SSLv3 are broken. We ban them outright.
317 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
317 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
318
318
319 if protocol == 'tls1.0':
319 if protocol == 'tls1.0':
320 # Defaults above are to use TLS 1.0+
320 # Defaults above are to use TLS 1.0+
321 pass
321 pass
322 elif protocol == 'tls1.1':
322 elif protocol == 'tls1.1':
323 options |= ssl.OP_NO_TLSv1
323 options |= ssl.OP_NO_TLSv1
324 elif protocol == 'tls1.2':
324 elif protocol == 'tls1.2':
325 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
325 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
326 else:
326 else:
327 raise error.Abort(_('this should not happen'))
327 raise error.Abort(_('this should not happen'))
328
328
329 # Prevent CRIME.
329 # Prevent CRIME.
330 # There is no guarantee this attribute is defined on the module.
330 # There is no guarantee this attribute is defined on the module.
331 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
331 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
332
332
333 return ssl.PROTOCOL_SSLv23, options, protocol
333 return ssl.PROTOCOL_SSLv23, options, protocol
334
334
335 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
335 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
336 """Add SSL/TLS to a socket.
336 """Add SSL/TLS to a socket.
337
337
338 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
338 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
339 choices based on what security options are available.
339 choices based on what security options are available.
340
340
341 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
341 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
342 the following additional arguments:
342 the following additional arguments:
343
343
344 * serverhostname - The expected hostname of the remote server. If the
344 * serverhostname - The expected hostname of the remote server. If the
345 server (and client) support SNI, this tells the server which certificate
345 server (and client) support SNI, this tells the server which certificate
346 to use.
346 to use.
347 """
347 """
348 if not serverhostname:
348 if not serverhostname:
349 raise error.Abort(_('serverhostname argument is required'))
349 raise error.Abort(_('serverhostname argument is required'))
350
350
351 for f in (keyfile, certfile):
351 for f in (keyfile, certfile):
352 if f and not os.path.exists(f):
352 if f and not os.path.exists(f):
353 raise error.Abort(
353 raise error.Abort(
354 _('certificate file (%s) does not exist; cannot connect to %s')
354 _('certificate file (%s) does not exist; cannot connect to %s')
355 % (f, pycompat.bytesurl(serverhostname)),
355 % (f, pycompat.bytesurl(serverhostname)),
356 hint=_('restore missing file or fix references '
356 hint=_('restore missing file or fix references '
357 'in Mercurial config'))
357 'in Mercurial config'))
358
358
359 settings = _hostsettings(ui, serverhostname)
359 settings = _hostsettings(ui, serverhostname)
360
360
361 # We can't use ssl.create_default_context() because it calls
361 # We can't use ssl.create_default_context() because it calls
362 # load_default_certs() unless CA arguments are passed to it. We want to
362 # load_default_certs() unless CA arguments are passed to it. We want to
363 # have explicit control over CA loading because implicitly loading
363 # have explicit control over CA loading because implicitly loading
364 # CAs may undermine the user's intent. For example, a user may define a CA
364 # CAs may undermine the user's intent. For example, a user may define a CA
365 # bundle with a specific CA cert removed. If the system/default CA bundle
365 # bundle with a specific CA cert removed. If the system/default CA bundle
366 # is loaded and contains that removed CA, you've just undone the user's
366 # is loaded and contains that removed CA, you've just undone the user's
367 # choice.
367 # choice.
368 sslcontext = SSLContext(settings['protocol'])
368 sslcontext = SSLContext(settings['protocol'])
369
369
370 # This is a no-op unless using modern ssl.
370 # This is a no-op unless using modern ssl.
371 sslcontext.options |= settings['ctxoptions']
371 sslcontext.options |= settings['ctxoptions']
372
372
373 # This still works on our fake SSLContext.
373 # This still works on our fake SSLContext.
374 sslcontext.verify_mode = settings['verifymode']
374 sslcontext.verify_mode = settings['verifymode']
375
375
376 if settings['ciphers']:
376 if settings['ciphers']:
377 try:
377 try:
378 sslcontext.set_ciphers(pycompat.sysstr(settings['ciphers']))
378 sslcontext.set_ciphers(pycompat.sysstr(settings['ciphers']))
379 except ssl.SSLError as e:
379 except ssl.SSLError as e:
380 raise error.Abort(
380 raise error.Abort(
381 _('could not set ciphers: %s')
381 _('could not set ciphers: %s')
382 % stringutil.forcebytestr(e.args[0]),
382 % stringutil.forcebytestr(e.args[0]),
383 hint=_('change cipher string (%s) in config') %
383 hint=_('change cipher string (%s) in config') %
384 settings['ciphers'])
384 settings['ciphers'])
385
385
386 if certfile is not None:
386 if certfile is not None:
387 def password():
387 def password():
388 f = keyfile or certfile
388 f = keyfile or certfile
389 return ui.getpass(_('passphrase for %s: ') % f, '')
389 return ui.getpass(_('passphrase for %s: ') % f, '')
390 sslcontext.load_cert_chain(certfile, keyfile, password)
390 sslcontext.load_cert_chain(certfile, keyfile, password)
391
391
392 if settings['cafile'] is not None:
392 if settings['cafile'] is not None:
393 try:
393 try:
394 sslcontext.load_verify_locations(cafile=settings['cafile'])
394 sslcontext.load_verify_locations(cafile=settings['cafile'])
395 except ssl.SSLError as e:
395 except ssl.SSLError as e:
396 if len(e.args) == 1: # pypy has different SSLError args
396 if len(e.args) == 1: # pypy has different SSLError args
397 msg = e.args[0]
397 msg = e.args[0]
398 else:
398 else:
399 msg = e.args[1]
399 msg = e.args[1]
400 raise error.Abort(_('error loading CA file %s: %s') % (
400 raise error.Abort(_('error loading CA file %s: %s') % (
401 settings['cafile'], stringutil.forcebytestr(msg)),
401 settings['cafile'], stringutil.forcebytestr(msg)),
402 hint=_('file is empty or malformed?'))
402 hint=_('file is empty or malformed?'))
403 caloaded = True
403 caloaded = True
404 elif settings['allowloaddefaultcerts']:
404 elif settings['allowloaddefaultcerts']:
405 # This is a no-op on old Python.
405 # This is a no-op on old Python.
406 sslcontext.load_default_certs()
406 sslcontext.load_default_certs()
407 caloaded = True
407 caloaded = True
408 else:
408 else:
409 caloaded = False
409 caloaded = False
410
410
411 try:
411 try:
412 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
412 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
413 except ssl.SSLError as e:
413 except ssl.SSLError as e:
414 # If we're doing certificate verification and no CA certs are loaded,
414 # If we're doing certificate verification and no CA certs are loaded,
415 # that is almost certainly the reason why verification failed. Provide
415 # that is almost certainly the reason why verification failed. Provide
416 # a hint to the user.
416 # a hint to the user.
417 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
417 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
418 # only show this warning if modern ssl is available.
418 # only show this warning if modern ssl is available.
419 # The exception handler is here to handle bugs around cert attributes:
419 # The exception handler is here to handle bugs around cert attributes:
420 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
420 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
421 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
421 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
422 # non-empty list, but the following conditional is otherwise True.
422 # non-empty list, but the following conditional is otherwise True.
423 try:
423 try:
424 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
424 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
425 modernssl and not sslcontext.get_ca_certs()):
425 modernssl and not sslcontext.get_ca_certs()):
426 ui.warn(_('(an attempt was made to load CA certificates but '
426 ui.warn(_('(an attempt was made to load CA certificates but '
427 'none were loaded; see '
427 'none were loaded; see '
428 'https://mercurial-scm.org/wiki/SecureConnections '
428 'https://mercurial-scm.org/wiki/SecureConnections '
429 'for how to configure Mercurial to avoid this '
429 'for how to configure Mercurial to avoid this '
430 'error)\n'))
430 'error)\n'))
431 except ssl.SSLError:
431 except ssl.SSLError:
432 pass
432 pass
433
433
434 # Try to print more helpful error messages for known failures.
434 # Try to print more helpful error messages for known failures.
435 if util.safehasattr(e, 'reason'):
435 if util.safehasattr(e, 'reason'):
436 # This error occurs when the client and server don't share a
436 # This error occurs when the client and server don't share a
437 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
437 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
438 # outright. Hopefully the reason for this error is that we require
438 # outright. Hopefully the reason for this error is that we require
439 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
439 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
440 # reason, try to emit an actionable warning.
440 # reason, try to emit an actionable warning.
441 if e.reason == r'UNSUPPORTED_PROTOCOL':
441 if e.reason == r'UNSUPPORTED_PROTOCOL':
442 # We attempted TLS 1.0+.
442 # We attempted TLS 1.0+.
443 if settings['protocolui'] == 'tls1.0':
443 if settings['protocolui'] == 'tls1.0':
444 # We support more than just TLS 1.0+. If this happens,
444 # We support more than just TLS 1.0+. If this happens,
445 # the likely scenario is either the client or the server
445 # the likely scenario is either the client or the server
446 # is really old. (e.g. server doesn't support TLS 1.0+ or
446 # is really old. (e.g. server doesn't support TLS 1.0+ or
447 # client doesn't support modern TLS versions introduced
447 # client doesn't support modern TLS versions introduced
448 # several years from when this comment was written).
448 # several years from when this comment was written).
449 if supportedprotocols != {'tls1.0'}:
449 if supportedprotocols != {'tls1.0'}:
450 ui.warn(_(
450 ui.warn(_(
451 '(could not communicate with %s using security '
451 '(could not communicate with %s using security '
452 'protocols %s; if you are using a modern Mercurial '
452 'protocols %s; if you are using a modern Mercurial '
453 'version, consider contacting the operator of this '
453 'version, consider contacting the operator of this '
454 'server; see '
454 'server; see '
455 'https://mercurial-scm.org/wiki/SecureConnections '
455 'https://mercurial-scm.org/wiki/SecureConnections '
456 'for more info)\n') % (
456 'for more info)\n') % (
457 pycompat.bytesurl(serverhostname),
457 pycompat.bytesurl(serverhostname),
458 ', '.join(sorted(supportedprotocols))))
458 ', '.join(sorted(supportedprotocols))))
459 else:
459 else:
460 ui.warn(_(
460 ui.warn(_(
461 '(could not communicate with %s using TLS 1.0; the '
461 '(could not communicate with %s using TLS 1.0; the '
462 'likely cause of this is the server no longer '
462 'likely cause of this is the server no longer '
463 'supports TLS 1.0 because it has known security '
463 'supports TLS 1.0 because it has known security '
464 'vulnerabilities; see '
464 'vulnerabilities; see '
465 'https://mercurial-scm.org/wiki/SecureConnections '
465 'https://mercurial-scm.org/wiki/SecureConnections '
466 'for more info)\n') %
466 'for more info)\n') %
467 pycompat.bytesurl(serverhostname))
467 pycompat.bytesurl(serverhostname))
468 else:
468 else:
469 # We attempted TLS 1.1+. We can only get here if the client
469 # We attempted TLS 1.1+. We can only get here if the client
470 # supports the configured protocol. So the likely reason is
470 # supports the configured protocol. So the likely reason is
471 # the client wants better security than the server can
471 # the client wants better security than the server can
472 # offer.
472 # offer.
473 ui.warn(_(
473 ui.warn(_(
474 '(could not negotiate a common security protocol (%s+) '
474 '(could not negotiate a common security protocol (%s+) '
475 'with %s; the likely cause is Mercurial is configured '
475 'with %s; the likely cause is Mercurial is configured '
476 'to be more secure than the server can support)\n') % (
476 'to be more secure than the server can support)\n') % (
477 settings['protocolui'],
477 settings['protocolui'],
478 pycompat.bytesurl(serverhostname)))
478 pycompat.bytesurl(serverhostname)))
479 ui.warn(_('(consider contacting the operator of this '
479 ui.warn(_('(consider contacting the operator of this '
480 'server and ask them to support modern TLS '
480 'server and ask them to support modern TLS '
481 'protocol versions; or, set '
481 'protocol versions; or, set '
482 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
482 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
483 'use of legacy, less secure protocols when '
483 'use of legacy, less secure protocols when '
484 'communicating with this server)\n') %
484 'communicating with this server)\n') %
485 pycompat.bytesurl(serverhostname))
485 pycompat.bytesurl(serverhostname))
486 ui.warn(_(
486 ui.warn(_(
487 '(see https://mercurial-scm.org/wiki/SecureConnections '
487 '(see https://mercurial-scm.org/wiki/SecureConnections '
488 'for more info)\n'))
488 'for more info)\n'))
489
489
490 elif (e.reason == r'CERTIFICATE_VERIFY_FAILED' and
490 elif (e.reason == r'CERTIFICATE_VERIFY_FAILED' and
491 pycompat.iswindows):
491 pycompat.iswindows):
492
492
493 ui.warn(_('(the full certificate chain may not be available '
493 ui.warn(_('(the full certificate chain may not be available '
494 'locally; see "hg help debugssl")\n'))
494 'locally; see "hg help debugssl")\n'))
495 raise
495 raise
496
496
497 # check if wrap_socket failed silently because socket had been
497 # check if wrap_socket failed silently because socket had been
498 # closed
498 # closed
499 # - see http://bugs.python.org/issue13721
499 # - see http://bugs.python.org/issue13721
500 if not sslsocket.cipher():
500 if not sslsocket.cipher():
501 raise error.Abort(_('ssl connection failed'))
501 raise error.Abort(_('ssl connection failed'))
502
502
503 sslsocket._hgstate = {
503 sslsocket._hgstate = {
504 'caloaded': caloaded,
504 'caloaded': caloaded,
505 'hostname': serverhostname,
505 'hostname': serverhostname,
506 'settings': settings,
506 'settings': settings,
507 'ui': ui,
507 'ui': ui,
508 }
508 }
509
509
510 return sslsocket
510 return sslsocket
511
511
512 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
512 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
513 requireclientcert=False):
513 requireclientcert=False):
514 """Wrap a socket for use by servers.
514 """Wrap a socket for use by servers.
515
515
516 ``certfile`` and ``keyfile`` specify the files containing the certificate's
516 ``certfile`` and ``keyfile`` specify the files containing the certificate's
517 public and private keys, respectively. Both keys can be defined in the same
517 public and private keys, respectively. Both keys can be defined in the same
518 file via ``certfile`` (the private key must come first in the file).
518 file via ``certfile`` (the private key must come first in the file).
519
519
520 ``cafile`` defines the path to certificate authorities.
520 ``cafile`` defines the path to certificate authorities.
521
521
522 ``requireclientcert`` specifies whether to require client certificates.
522 ``requireclientcert`` specifies whether to require client certificates.
523
523
524 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
524 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
525 """
525 """
526 # This function is not used much by core Mercurial, so the error messaging
526 # This function is not used much by core Mercurial, so the error messaging
527 # doesn't have to be as detailed as for wrapsocket().
527 # doesn't have to be as detailed as for wrapsocket().
528 for f in (certfile, keyfile, cafile):
528 for f in (certfile, keyfile, cafile):
529 if f and not os.path.exists(f):
529 if f and not os.path.exists(f):
530 raise error.Abort(_('referenced certificate file (%s) does not '
530 raise error.Abort(_('referenced certificate file (%s) does not '
531 'exist') % f)
531 'exist') % f)
532
532
533 protocol, options, _protocolui = protocolsettings('tls1.0')
533 protocol, options, _protocolui = protocolsettings('tls1.0')
534
534
535 # This config option is intended for use in tests only. It is a giant
535 # This config option is intended for use in tests only. It is a giant
536 # footgun to kill security. Don't define it.
536 # footgun to kill security. Don't define it.
537 exactprotocol = ui.config('devel', 'serverexactprotocol')
537 exactprotocol = ui.config('devel', 'serverexactprotocol')
538 if exactprotocol == 'tls1.0':
538 if exactprotocol == 'tls1.0':
539 protocol = ssl.PROTOCOL_TLSv1
539 protocol = ssl.PROTOCOL_TLSv1
540 elif exactprotocol == 'tls1.1':
540 elif exactprotocol == 'tls1.1':
541 if 'tls1.1' not in supportedprotocols:
541 if 'tls1.1' not in supportedprotocols:
542 raise error.Abort(_('TLS 1.1 not supported by this Python'))
542 raise error.Abort(_('TLS 1.1 not supported by this Python'))
543 protocol = ssl.PROTOCOL_TLSv1_1
543 protocol = ssl.PROTOCOL_TLSv1_1
544 elif exactprotocol == 'tls1.2':
544 elif exactprotocol == 'tls1.2':
545 if 'tls1.2' not in supportedprotocols:
545 if 'tls1.2' not in supportedprotocols:
546 raise error.Abort(_('TLS 1.2 not supported by this Python'))
546 raise error.Abort(_('TLS 1.2 not supported by this Python'))
547 protocol = ssl.PROTOCOL_TLSv1_2
547 protocol = ssl.PROTOCOL_TLSv1_2
548 elif exactprotocol:
548 elif exactprotocol:
549 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
549 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
550 exactprotocol)
550 exactprotocol)
551
551
552 if modernssl:
552 if modernssl:
553 # We /could/ use create_default_context() here since it doesn't load
553 # We /could/ use create_default_context() here since it doesn't load
554 # CAs when configured for client auth. However, it is hard-coded to
554 # CAs when configured for client auth. However, it is hard-coded to
555 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
555 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
556 sslcontext = SSLContext(protocol)
556 sslcontext = SSLContext(protocol)
557 sslcontext.options |= options
557 sslcontext.options |= options
558
558
559 # Improve forward secrecy.
559 # Improve forward secrecy.
560 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
560 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
561 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
561 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
562
562
563 # Use the list of more secure ciphers if found in the ssl module.
563 # Use the list of more secure ciphers if found in the ssl module.
564 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
564 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
565 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
565 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
566 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
566 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
567 else:
567 else:
568 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
568 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
569
569
570 if requireclientcert:
570 if requireclientcert:
571 sslcontext.verify_mode = ssl.CERT_REQUIRED
571 sslcontext.verify_mode = ssl.CERT_REQUIRED
572 else:
572 else:
573 sslcontext.verify_mode = ssl.CERT_NONE
573 sslcontext.verify_mode = ssl.CERT_NONE
574
574
575 if certfile or keyfile:
575 if certfile or keyfile:
576 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
576 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
577
577
578 if cafile:
578 if cafile:
579 sslcontext.load_verify_locations(cafile=cafile)
579 sslcontext.load_verify_locations(cafile=cafile)
580
580
581 return sslcontext.wrap_socket(sock, server_side=True)
581 return sslcontext.wrap_socket(sock, server_side=True)
582
582
583 class wildcarderror(Exception):
583 class wildcarderror(Exception):
584 """Represents an error parsing wildcards in DNS name."""
584 """Represents an error parsing wildcards in DNS name."""
585
585
586 def _dnsnamematch(dn, hostname, maxwildcards=1):
586 def _dnsnamematch(dn, hostname, maxwildcards=1):
587 """Match DNS names according RFC 6125 section 6.4.3.
587 """Match DNS names according RFC 6125 section 6.4.3.
588
588
589 This code is effectively copied from CPython's ssl._dnsname_match.
589 This code is effectively copied from CPython's ssl._dnsname_match.
590
590
591 Returns a bool indicating whether the expected hostname matches
591 Returns a bool indicating whether the expected hostname matches
592 the value in ``dn``.
592 the value in ``dn``.
593 """
593 """
594 pats = []
594 pats = []
595 if not dn:
595 if not dn:
596 return False
596 return False
597 dn = pycompat.bytesurl(dn)
597 dn = pycompat.bytesurl(dn)
598 hostname = pycompat.bytesurl(hostname)
598 hostname = pycompat.bytesurl(hostname)
599
599
600 pieces = dn.split('.')
600 pieces = dn.split('.')
601 leftmost = pieces[0]
601 leftmost = pieces[0]
602 remainder = pieces[1:]
602 remainder = pieces[1:]
603 wildcards = leftmost.count('*')
603 wildcards = leftmost.count('*')
604 if wildcards > maxwildcards:
604 if wildcards > maxwildcards:
605 raise wildcarderror(
605 raise wildcarderror(
606 _('too many wildcards in certificate DNS name: %s') % dn)
606 _('too many wildcards in certificate DNS name: %s') % dn)
607
607
608 # speed up common case w/o wildcards
608 # speed up common case w/o wildcards
609 if not wildcards:
609 if not wildcards:
610 return dn.lower() == hostname.lower()
610 return dn.lower() == hostname.lower()
611
611
612 # RFC 6125, section 6.4.3, subitem 1.
612 # RFC 6125, section 6.4.3, subitem 1.
613 # The client SHOULD NOT attempt to match a presented identifier in which
613 # The client SHOULD NOT attempt to match a presented identifier in which
614 # the wildcard character comprises a label other than the left-most label.
614 # the wildcard character comprises a label other than the left-most label.
615 if leftmost == '*':
615 if leftmost == '*':
616 # When '*' is a fragment by itself, it matches a non-empty dotless
616 # When '*' is a fragment by itself, it matches a non-empty dotless
617 # fragment.
617 # fragment.
618 pats.append('[^.]+')
618 pats.append('[^.]+')
619 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
619 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
620 # RFC 6125, section 6.4.3, subitem 3.
620 # RFC 6125, section 6.4.3, subitem 3.
621 # The client SHOULD NOT attempt to match a presented identifier
621 # The client SHOULD NOT attempt to match a presented identifier
622 # where the wildcard character is embedded within an A-label or
622 # where the wildcard character is embedded within an A-label or
623 # U-label of an internationalized domain name.
623 # U-label of an internationalized domain name.
624 pats.append(stringutil.reescape(leftmost))
624 pats.append(stringutil.reescape(leftmost))
625 else:
625 else:
626 # Otherwise, '*' matches any dotless string, e.g. www*
626 # Otherwise, '*' matches any dotless string, e.g. www*
627 pats.append(stringutil.reescape(leftmost).replace(br'\*', '[^.]*'))
627 pats.append(stringutil.reescape(leftmost).replace(br'\*', '[^.]*'))
628
628
629 # add the remaining fragments, ignore any wildcards
629 # add the remaining fragments, ignore any wildcards
630 for frag in remainder:
630 for frag in remainder:
631 pats.append(stringutil.reescape(frag))
631 pats.append(stringutil.reescape(frag))
632
632
633 pat = re.compile(br'\A' + br'\.'.join(pats) + br'\Z', re.IGNORECASE)
633 pat = re.compile(br'\A' + br'\.'.join(pats) + br'\Z', re.IGNORECASE)
634 return pat.match(hostname) is not None
634 return pat.match(hostname) is not None
635
635
636 def _verifycert(cert, hostname):
636 def _verifycert(cert, hostname):
637 '''Verify that cert (in socket.getpeercert() format) matches hostname.
637 '''Verify that cert (in socket.getpeercert() format) matches hostname.
638 CRLs is not handled.
638 CRLs is not handled.
639
639
640 Returns error message if any problems are found and None on success.
640 Returns error message if any problems are found and None on success.
641 '''
641 '''
642 if not cert:
642 if not cert:
643 return _('no certificate received')
643 return _('no certificate received')
644
644
645 dnsnames = []
645 dnsnames = []
646 san = cert.get(r'subjectAltName', [])
646 san = cert.get(r'subjectAltName', [])
647 for key, value in san:
647 for key, value in san:
648 if key == r'DNS':
648 if key == r'DNS':
649 try:
649 try:
650 if _dnsnamematch(value, hostname):
650 if _dnsnamematch(value, hostname):
651 return
651 return
652 except wildcarderror as e:
652 except wildcarderror as e:
653 return stringutil.forcebytestr(e.args[0])
653 return stringutil.forcebytestr(e.args[0])
654
654
655 dnsnames.append(value)
655 dnsnames.append(value)
656
656
657 if not dnsnames:
657 if not dnsnames:
658 # The subject is only checked when there is no DNS in subjectAltName.
658 # The subject is only checked when there is no DNS in subjectAltName.
659 for sub in cert.get(r'subject', []):
659 for sub in cert.get(r'subject', []):
660 for key, value in sub:
660 for key, value in sub:
661 # According to RFC 2818 the most specific Common Name must
661 # According to RFC 2818 the most specific Common Name must
662 # be used.
662 # be used.
663 if key == r'commonName':
663 if key == r'commonName':
664 # 'subject' entries are unicode.
664 # 'subject' entries are unicode.
665 try:
665 try:
666 value = value.encode('ascii')
666 value = value.encode('ascii')
667 except UnicodeEncodeError:
667 except UnicodeEncodeError:
668 return _('IDN in certificate not supported')
668 return _('IDN in certificate not supported')
669
669
670 try:
670 try:
671 if _dnsnamematch(value, hostname):
671 if _dnsnamematch(value, hostname):
672 return
672 return
673 except wildcarderror as e:
673 except wildcarderror as e:
674 return stringutil.forcebytestr(e.args[0])
674 return stringutil.forcebytestr(e.args[0])
675
675
676 dnsnames.append(value)
676 dnsnames.append(value)
677
677
678 dnsnames = [pycompat.bytesurl(d) for d in dnsnames]
678 dnsnames = [pycompat.bytesurl(d) for d in dnsnames]
679 if len(dnsnames) > 1:
679 if len(dnsnames) > 1:
680 return _('certificate is for %s') % ', '.join(dnsnames)
680 return _('certificate is for %s') % ', '.join(dnsnames)
681 elif len(dnsnames) == 1:
681 elif len(dnsnames) == 1:
682 return _('certificate is for %s') % dnsnames[0]
682 return _('certificate is for %s') % dnsnames[0]
683 else:
683 else:
684 return _('no commonName or subjectAltName found in certificate')
684 return _('no commonName or subjectAltName found in certificate')
685
685
686 def _plainapplepython():
686 def _plainapplepython():
687 """return true if this seems to be a pure Apple Python that
687 """return true if this seems to be a pure Apple Python that
688 * is unfrozen and presumably has the whole mercurial module in the file
688 * is unfrozen and presumably has the whole mercurial module in the file
689 system
689 system
690 * presumably is an Apple Python that uses Apple OpenSSL which has patches
690 * presumably is an Apple Python that uses Apple OpenSSL which has patches
691 for using system certificate store CAs in addition to the provided
691 for using system certificate store CAs in addition to the provided
692 cacerts file
692 cacerts file
693 """
693 """
694 if (not pycompat.isdarwin or procutil.mainfrozen() or
694 if (not pycompat.isdarwin or procutil.mainfrozen() or
695 not pycompat.sysexecutable):
695 not pycompat.sysexecutable):
696 return False
696 return False
697 exe = os.path.realpath(pycompat.sysexecutable).lower()
697 exe = os.path.realpath(pycompat.sysexecutable).lower()
698 return (exe.startswith('/usr/bin/python') or
698 return (exe.startswith('/usr/bin/python') or
699 exe.startswith('/system/library/frameworks/python.framework/'))
699 exe.startswith('/system/library/frameworks/python.framework/'))
700
700
701 _systemcacertpaths = [
701 _systemcacertpaths = [
702 # RHEL, CentOS, and Fedora
702 # RHEL, CentOS, and Fedora
703 '/etc/pki/tls/certs/ca-bundle.trust.crt',
703 '/etc/pki/tls/certs/ca-bundle.trust.crt',
704 # Debian, Ubuntu, Gentoo
704 # Debian, Ubuntu, Gentoo
705 '/etc/ssl/certs/ca-certificates.crt',
705 '/etc/ssl/certs/ca-certificates.crt',
706 ]
706 ]
707
707
708 def _defaultcacerts(ui):
708 def _defaultcacerts(ui):
709 """return path to default CA certificates or None.
709 """return path to default CA certificates or None.
710
710
711 It is assumed this function is called when the returned certificates
711 It is assumed this function is called when the returned certificates
712 file will actually be used to validate connections. Therefore this
712 file will actually be used to validate connections. Therefore this
713 function may print warnings or debug messages assuming this usage.
713 function may print warnings or debug messages assuming this usage.
714
714
715 We don't print a message when the Python is able to load default
715 We don't print a message when the Python is able to load default
716 CA certs because this scenario is detected at socket connect time.
716 CA certs because this scenario is detected at socket connect time.
717 """
717 """
718 # The "certifi" Python package provides certificates. If it is installed
718 # The "certifi" Python package provides certificates. If it is installed
719 # and usable, assume the user intends it to be used and use it.
719 # and usable, assume the user intends it to be used and use it.
720 try:
720 try:
721 import certifi
721 import certifi
722 certs = certifi.where()
722 certs = certifi.where()
723 if os.path.exists(certs):
723 if os.path.exists(certs):
724 ui.debug('using ca certificates from certifi\n')
724 ui.debug('using ca certificates from certifi\n')
725 return certs
725 return pycompat.fsencode(certs)
726 except (ImportError, AttributeError):
726 except (ImportError, AttributeError):
727 pass
727 pass
728
728
729 # On Windows, only the modern ssl module is capable of loading the system
729 # On Windows, only the modern ssl module is capable of loading the system
730 # CA certificates. If we're not capable of doing that, emit a warning
730 # CA certificates. If we're not capable of doing that, emit a warning
731 # because we'll get a certificate verification error later and the lack
731 # because we'll get a certificate verification error later and the lack
732 # of loaded CA certificates will be the reason why.
732 # of loaded CA certificates will be the reason why.
733 # Assertion: this code is only called if certificates are being verified.
733 # Assertion: this code is only called if certificates are being verified.
734 if pycompat.iswindows:
734 if pycompat.iswindows:
735 if not _canloaddefaultcerts:
735 if not _canloaddefaultcerts:
736 ui.warn(_('(unable to load Windows CA certificates; see '
736 ui.warn(_('(unable to load Windows CA certificates; see '
737 'https://mercurial-scm.org/wiki/SecureConnections for '
737 'https://mercurial-scm.org/wiki/SecureConnections for '
738 'how to configure Mercurial to avoid this message)\n'))
738 'how to configure Mercurial to avoid this message)\n'))
739
739
740 return None
740 return None
741
741
742 # Apple's OpenSSL has patches that allow a specially constructed certificate
742 # Apple's OpenSSL has patches that allow a specially constructed certificate
743 # to load the system CA store. If we're running on Apple Python, use this
743 # to load the system CA store. If we're running on Apple Python, use this
744 # trick.
744 # trick.
745 if _plainapplepython():
745 if _plainapplepython():
746 dummycert = os.path.join(
746 dummycert = os.path.join(
747 os.path.dirname(pycompat.fsencode(__file__)), 'dummycert.pem')
747 os.path.dirname(pycompat.fsencode(__file__)), 'dummycert.pem')
748 if os.path.exists(dummycert):
748 if os.path.exists(dummycert):
749 return dummycert
749 return dummycert
750
750
751 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
751 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
752 # load system certs, we're out of luck.
752 # load system certs, we're out of luck.
753 if pycompat.isdarwin:
753 if pycompat.isdarwin:
754 # FUTURE Consider looking for Homebrew or MacPorts installed certs
754 # FUTURE Consider looking for Homebrew or MacPorts installed certs
755 # files. Also consider exporting the keychain certs to a file during
755 # files. Also consider exporting the keychain certs to a file during
756 # Mercurial install.
756 # Mercurial install.
757 if not _canloaddefaultcerts:
757 if not _canloaddefaultcerts:
758 ui.warn(_('(unable to load CA certificates; see '
758 ui.warn(_('(unable to load CA certificates; see '
759 'https://mercurial-scm.org/wiki/SecureConnections for '
759 'https://mercurial-scm.org/wiki/SecureConnections for '
760 'how to configure Mercurial to avoid this message)\n'))
760 'how to configure Mercurial to avoid this message)\n'))
761 return None
761 return None
762
762
763 # / is writable on Windows. Out of an abundance of caution make sure
763 # / is writable on Windows. Out of an abundance of caution make sure
764 # we're not on Windows because paths from _systemcacerts could be installed
764 # we're not on Windows because paths from _systemcacerts could be installed
765 # by non-admin users.
765 # by non-admin users.
766 assert not pycompat.iswindows
766 assert not pycompat.iswindows
767
767
768 # Try to find CA certificates in well-known locations. We print a warning
768 # Try to find CA certificates in well-known locations. We print a warning
769 # when using a found file because we don't want too much silent magic
769 # when using a found file because we don't want too much silent magic
770 # for security settings. The expectation is that proper Mercurial
770 # for security settings. The expectation is that proper Mercurial
771 # installs will have the CA certs path defined at install time and the
771 # installs will have the CA certs path defined at install time and the
772 # installer/packager will make an appropriate decision on the user's
772 # installer/packager will make an appropriate decision on the user's
773 # behalf. We only get here and perform this setting as a feature of
773 # behalf. We only get here and perform this setting as a feature of
774 # last resort.
774 # last resort.
775 if not _canloaddefaultcerts:
775 if not _canloaddefaultcerts:
776 for path in _systemcacertpaths:
776 for path in _systemcacertpaths:
777 if os.path.isfile(path):
777 if os.path.isfile(path):
778 ui.warn(_('(using CA certificates from %s; if you see this '
778 ui.warn(_('(using CA certificates from %s; if you see this '
779 'message, your Mercurial install is not properly '
779 'message, your Mercurial install is not properly '
780 'configured; see '
780 'configured; see '
781 'https://mercurial-scm.org/wiki/SecureConnections '
781 'https://mercurial-scm.org/wiki/SecureConnections '
782 'for how to configure Mercurial to avoid this '
782 'for how to configure Mercurial to avoid this '
783 'message)\n') % path)
783 'message)\n') % path)
784 return path
784 return path
785
785
786 ui.warn(_('(unable to load CA certificates; see '
786 ui.warn(_('(unable to load CA certificates; see '
787 'https://mercurial-scm.org/wiki/SecureConnections for '
787 'https://mercurial-scm.org/wiki/SecureConnections for '
788 'how to configure Mercurial to avoid this message)\n'))
788 'how to configure Mercurial to avoid this message)\n'))
789
789
790 return None
790 return None
791
791
792 def validatesocket(sock):
792 def validatesocket(sock):
793 """Validate a socket meets security requirements.
793 """Validate a socket meets security requirements.
794
794
795 The passed socket must have been created with ``wrapsocket()``.
795 The passed socket must have been created with ``wrapsocket()``.
796 """
796 """
797 shost = sock._hgstate['hostname']
797 shost = sock._hgstate['hostname']
798 host = pycompat.bytesurl(shost)
798 host = pycompat.bytesurl(shost)
799 ui = sock._hgstate['ui']
799 ui = sock._hgstate['ui']
800 settings = sock._hgstate['settings']
800 settings = sock._hgstate['settings']
801
801
802 try:
802 try:
803 peercert = sock.getpeercert(True)
803 peercert = sock.getpeercert(True)
804 peercert2 = sock.getpeercert()
804 peercert2 = sock.getpeercert()
805 except AttributeError:
805 except AttributeError:
806 raise error.Abort(_('%s ssl connection error') % host)
806 raise error.Abort(_('%s ssl connection error') % host)
807
807
808 if not peercert:
808 if not peercert:
809 raise error.Abort(_('%s certificate error: '
809 raise error.Abort(_('%s certificate error: '
810 'no certificate received') % host)
810 'no certificate received') % host)
811
811
812 if settings['disablecertverification']:
812 if settings['disablecertverification']:
813 # We don't print the certificate fingerprint because it shouldn't
813 # We don't print the certificate fingerprint because it shouldn't
814 # be necessary: if the user requested certificate verification be
814 # be necessary: if the user requested certificate verification be
815 # disabled, they presumably already saw a message about the inability
815 # disabled, they presumably already saw a message about the inability
816 # to verify the certificate and this message would have printed the
816 # to verify the certificate and this message would have printed the
817 # fingerprint. So printing the fingerprint here adds little to no
817 # fingerprint. So printing the fingerprint here adds little to no
818 # value.
818 # value.
819 ui.warn(_('warning: connection security to %s is disabled per current '
819 ui.warn(_('warning: connection security to %s is disabled per current '
820 'settings; communication is susceptible to eavesdropping '
820 'settings; communication is susceptible to eavesdropping '
821 'and tampering\n') % host)
821 'and tampering\n') % host)
822 return
822 return
823
823
824 # If a certificate fingerprint is pinned, use it and only it to
824 # If a certificate fingerprint is pinned, use it and only it to
825 # validate the remote cert.
825 # validate the remote cert.
826 peerfingerprints = {
826 peerfingerprints = {
827 'sha1': node.hex(hashlib.sha1(peercert).digest()),
827 'sha1': node.hex(hashlib.sha1(peercert).digest()),
828 'sha256': node.hex(hashlib.sha256(peercert).digest()),
828 'sha256': node.hex(hashlib.sha256(peercert).digest()),
829 'sha512': node.hex(hashlib.sha512(peercert).digest()),
829 'sha512': node.hex(hashlib.sha512(peercert).digest()),
830 }
830 }
831
831
832 def fmtfingerprint(s):
832 def fmtfingerprint(s):
833 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
833 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
834
834
835 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
835 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
836
836
837 if settings['certfingerprints']:
837 if settings['certfingerprints']:
838 for hash, fingerprint in settings['certfingerprints']:
838 for hash, fingerprint in settings['certfingerprints']:
839 if peerfingerprints[hash].lower() == fingerprint:
839 if peerfingerprints[hash].lower() == fingerprint:
840 ui.debug('%s certificate matched fingerprint %s:%s\n' %
840 ui.debug('%s certificate matched fingerprint %s:%s\n' %
841 (host, hash, fmtfingerprint(fingerprint)))
841 (host, hash, fmtfingerprint(fingerprint)))
842 if settings['legacyfingerprint']:
842 if settings['legacyfingerprint']:
843 ui.warn(_('(SHA-1 fingerprint for %s found in legacy '
843 ui.warn(_('(SHA-1 fingerprint for %s found in legacy '
844 '[hostfingerprints] section; '
844 '[hostfingerprints] section; '
845 'if you trust this fingerprint, remove the old '
845 'if you trust this fingerprint, remove the old '
846 'SHA-1 fingerprint from [hostfingerprints] and '
846 'SHA-1 fingerprint from [hostfingerprints] and '
847 'add the following entry to the new '
847 'add the following entry to the new '
848 '[hostsecurity] section: %s:fingerprints=%s)\n') %
848 '[hostsecurity] section: %s:fingerprints=%s)\n') %
849 (host, host, nicefingerprint))
849 (host, host, nicefingerprint))
850 return
850 return
851
851
852 # Pinned fingerprint didn't match. This is a fatal error.
852 # Pinned fingerprint didn't match. This is a fatal error.
853 if settings['legacyfingerprint']:
853 if settings['legacyfingerprint']:
854 section = 'hostfingerprint'
854 section = 'hostfingerprint'
855 nice = fmtfingerprint(peerfingerprints['sha1'])
855 nice = fmtfingerprint(peerfingerprints['sha1'])
856 else:
856 else:
857 section = 'hostsecurity'
857 section = 'hostsecurity'
858 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
858 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
859 raise error.Abort(_('certificate for %s has unexpected '
859 raise error.Abort(_('certificate for %s has unexpected '
860 'fingerprint %s') % (host, nice),
860 'fingerprint %s') % (host, nice),
861 hint=_('check %s configuration') % section)
861 hint=_('check %s configuration') % section)
862
862
863 # Security is enabled but no CAs are loaded. We can't establish trust
863 # Security is enabled but no CAs are loaded. We can't establish trust
864 # for the cert so abort.
864 # for the cert so abort.
865 if not sock._hgstate['caloaded']:
865 if not sock._hgstate['caloaded']:
866 raise error.Abort(
866 raise error.Abort(
867 _('unable to verify security of %s (no loaded CA certificates); '
867 _('unable to verify security of %s (no loaded CA certificates); '
868 'refusing to connect') % host,
868 'refusing to connect') % host,
869 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
869 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
870 'how to configure Mercurial to avoid this error or set '
870 'how to configure Mercurial to avoid this error or set '
871 'hostsecurity.%s:fingerprints=%s to trust this server') %
871 'hostsecurity.%s:fingerprints=%s to trust this server') %
872 (host, nicefingerprint))
872 (host, nicefingerprint))
873
873
874 msg = _verifycert(peercert2, shost)
874 msg = _verifycert(peercert2, shost)
875 if msg:
875 if msg:
876 raise error.Abort(_('%s certificate error: %s') % (host, msg),
876 raise error.Abort(_('%s certificate error: %s') % (host, msg),
877 hint=_('set hostsecurity.%s:certfingerprints=%s '
877 hint=_('set hostsecurity.%s:certfingerprints=%s '
878 'config setting or use --insecure to connect '
878 'config setting or use --insecure to connect '
879 'insecurely') %
879 'insecurely') %
880 (host, nicefingerprint))
880 (host, nicefingerprint))
@@ -1,1881 +1,1893 b''
1 Set up a repo
1 Set up a repo
2
2
3 $ cat <<EOF >> $HGRCPATH
3 $ cat <<EOF >> $HGRCPATH
4 > [ui]
4 > [ui]
5 > interactive = true
5 > interactive = true
6 > [extensions]
6 > [extensions]
7 > record =
7 > record =
8 > EOF
8 > EOF
9
9
10 $ hg init a
10 $ hg init a
11 $ cd a
11 $ cd a
12
12
13 Select no files
13 Select no files
14
14
15 $ touch empty-rw
15 $ touch empty-rw
16 $ hg add empty-rw
16 $ hg add empty-rw
17
17
18 $ hg record --config ui.interactive=false
18 $ hg record --config ui.interactive=false
19 abort: running non-interactively, use commit instead
19 abort: running non-interactively, use commit instead
20 [255]
20 [255]
21 $ hg commit -i --config ui.interactive=false
21 $ hg commit -i --config ui.interactive=false
22 abort: running non-interactively
22 abort: running non-interactively
23 [255]
23 [255]
24 $ hg commit -i empty-rw<<EOF
24 $ hg commit -i empty-rw<<EOF
25 > n
25 > n
26 > EOF
26 > EOF
27 diff --git a/empty-rw b/empty-rw
27 diff --git a/empty-rw b/empty-rw
28 new file mode 100644
28 new file mode 100644
29 abort: empty commit message
29 abort: empty commit message
30 [255]
30 [255]
31
31
32 $ hg tip -p
32 $ hg tip -p
33 changeset: -1:000000000000
33 changeset: -1:000000000000
34 tag: tip
34 tag: tip
35 user:
35 user:
36 date: Thu Jan 01 00:00:00 1970 +0000
36 date: Thu Jan 01 00:00:00 1970 +0000
37
37
38
38
39
39
40 Select files but no hunks
40 Select files but no hunks
41
41
42 $ hg commit -i empty-rw<<EOF
42 $ hg commit -i empty-rw<<EOF
43 > y
43 > y
44 > n
44 > n
45 > EOF
45 > EOF
46 diff --git a/empty-rw b/empty-rw
46 diff --git a/empty-rw b/empty-rw
47 new file mode 100644
47 new file mode 100644
48 abort: empty commit message
48 abort: empty commit message
49 [255]
49 [255]
50
50
51 $ hg tip -p
51 $ hg tip -p
52 changeset: -1:000000000000
52 changeset: -1:000000000000
53 tag: tip
53 tag: tip
54 user:
54 user:
55 date: Thu Jan 01 00:00:00 1970 +0000
55 date: Thu Jan 01 00:00:00 1970 +0000
56
56
57
57
58
58
59 Abort for untracked
59 Abort for untracked
60
60
61 $ touch untracked
61 $ touch untracked
62 $ hg commit -i -m should-fail empty-rw untracked
62 $ hg commit -i -m should-fail empty-rw untracked
63 abort: untracked: file not tracked!
63 abort: untracked: file not tracked!
64 [255]
64 [255]
65 $ rm untracked
65 $ rm untracked
66
66
67 Record empty file
67 Record empty file
68
68
69 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
69 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
70 > y
70 > y
71 > EOF
71 > EOF
72 diff --git a/empty-rw b/empty-rw
72 diff --git a/empty-rw b/empty-rw
73 new file mode 100644
73 new file mode 100644
74
74
75 $ hg tip -p
75 $ hg tip -p
76 changeset: 0:c0708cf4e46e
76 changeset: 0:c0708cf4e46e
77 tag: tip
77 tag: tip
78 user: test
78 user: test
79 date: Thu Jan 01 00:00:00 1970 +0000
79 date: Thu Jan 01 00:00:00 1970 +0000
80 summary: empty
80 summary: empty
81
81
82
82
83
83
84 Summary shows we updated to the new cset
84 Summary shows we updated to the new cset
85
85
86 $ hg summary
86 $ hg summary
87 parent: 0:c0708cf4e46e tip
87 parent: 0:c0708cf4e46e tip
88 empty
88 empty
89 branch: default
89 branch: default
90 commit: (clean)
90 commit: (clean)
91 update: (current)
91 update: (current)
92 phases: 1 draft
92 phases: 1 draft
93
93
94 Rename empty file
94 Rename empty file
95
95
96 $ hg mv empty-rw empty-rename
96 $ hg mv empty-rw empty-rename
97 $ hg commit -i -d '1 0' -m rename<<EOF
97 $ hg commit -i -d '1 0' -m rename<<EOF
98 > y
98 > y
99 > EOF
99 > EOF
100 diff --git a/empty-rw b/empty-rename
100 diff --git a/empty-rw b/empty-rename
101 rename from empty-rw
101 rename from empty-rw
102 rename to empty-rename
102 rename to empty-rename
103 examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
103 examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
104
104
105
105
106 $ hg tip -p
106 $ hg tip -p
107 changeset: 1:d695e8dcb197
107 changeset: 1:d695e8dcb197
108 tag: tip
108 tag: tip
109 user: test
109 user: test
110 date: Thu Jan 01 00:00:01 1970 +0000
110 date: Thu Jan 01 00:00:01 1970 +0000
111 summary: rename
111 summary: rename
112
112
113
113
114
114
115 Copy empty file
115 Copy empty file
116
116
117 $ hg cp empty-rename empty-copy
117 $ hg cp empty-rename empty-copy
118 $ hg commit -i -d '2 0' -m copy<<EOF
118 $ hg commit -i -d '2 0' -m copy<<EOF
119 > y
119 > y
120 > EOF
120 > EOF
121 diff --git a/empty-rename b/empty-copy
121 diff --git a/empty-rename b/empty-copy
122 copy from empty-rename
122 copy from empty-rename
123 copy to empty-copy
123 copy to empty-copy
124 examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
124 examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
125
125
126
126
127 $ hg tip -p
127 $ hg tip -p
128 changeset: 2:1d4b90bea524
128 changeset: 2:1d4b90bea524
129 tag: tip
129 tag: tip
130 user: test
130 user: test
131 date: Thu Jan 01 00:00:02 1970 +0000
131 date: Thu Jan 01 00:00:02 1970 +0000
132 summary: copy
132 summary: copy
133
133
134
134
135
135
136 Delete empty file
136 Delete empty file
137
137
138 $ hg rm empty-copy
138 $ hg rm empty-copy
139 $ hg commit -i -d '3 0' -m delete<<EOF
139 $ hg commit -i -d '3 0' -m delete<<EOF
140 > y
140 > y
141 > EOF
141 > EOF
142 diff --git a/empty-copy b/empty-copy
142 diff --git a/empty-copy b/empty-copy
143 deleted file mode 100644
143 deleted file mode 100644
144 examine changes to 'empty-copy'? [Ynesfdaq?] y
144 examine changes to 'empty-copy'? [Ynesfdaq?] y
145
145
146
146
147 $ hg tip -p
147 $ hg tip -p
148 changeset: 3:b39a238f01a1
148 changeset: 3:b39a238f01a1
149 tag: tip
149 tag: tip
150 user: test
150 user: test
151 date: Thu Jan 01 00:00:03 1970 +0000
151 date: Thu Jan 01 00:00:03 1970 +0000
152 summary: delete
152 summary: delete
153
153
154
154
155
155
156 Add binary file
156 Add binary file
157
157
158 $ hg bundle --type v1 --base -2 tip.bundle
158 $ hg bundle --type v1 --base -2 tip.bundle
159 1 changesets found
159 1 changesets found
160 $ hg add tip.bundle
160 $ hg add tip.bundle
161 $ hg commit -i -d '4 0' -m binary<<EOF
161 $ hg commit -i -d '4 0' -m binary<<EOF
162 > y
162 > y
163 > EOF
163 > EOF
164 diff --git a/tip.bundle b/tip.bundle
164 diff --git a/tip.bundle b/tip.bundle
165 new file mode 100644
165 new file mode 100644
166 this is a binary file
166 this is a binary file
167 examine changes to 'tip.bundle'? [Ynesfdaq?] y
167 examine changes to 'tip.bundle'? [Ynesfdaq?] y
168
168
169
169
170 $ hg tip -p
170 $ hg tip -p
171 changeset: 4:ad816da3711e
171 changeset: 4:ad816da3711e
172 tag: tip
172 tag: tip
173 user: test
173 user: test
174 date: Thu Jan 01 00:00:04 1970 +0000
174 date: Thu Jan 01 00:00:04 1970 +0000
175 summary: binary
175 summary: binary
176
176
177 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
177 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
178 Binary file tip.bundle has changed
178 Binary file tip.bundle has changed
179
179
180
180
181 Change binary file
181 Change binary file
182
182
183 $ hg bundle --base -2 --type v1 tip.bundle
183 $ hg bundle --base -2 --type v1 tip.bundle
184 1 changesets found
184 1 changesets found
185 $ hg commit -i -d '5 0' -m binary-change<<EOF
185 $ hg commit -i -d '5 0' -m binary-change<<EOF
186 > y
186 > y
187 > EOF
187 > EOF
188 diff --git a/tip.bundle b/tip.bundle
188 diff --git a/tip.bundle b/tip.bundle
189 this modifies a binary file (all or nothing)
189 this modifies a binary file (all or nothing)
190 examine changes to 'tip.bundle'? [Ynesfdaq?] y
190 examine changes to 'tip.bundle'? [Ynesfdaq?] y
191
191
192
192
193 $ hg tip -p
193 $ hg tip -p
194 changeset: 5:dccd6f3eb485
194 changeset: 5:dccd6f3eb485
195 tag: tip
195 tag: tip
196 user: test
196 user: test
197 date: Thu Jan 01 00:00:05 1970 +0000
197 date: Thu Jan 01 00:00:05 1970 +0000
198 summary: binary-change
198 summary: binary-change
199
199
200 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
200 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
201 Binary file tip.bundle has changed
201 Binary file tip.bundle has changed
202
202
203
203
204 Rename and change binary file
204 Rename and change binary file
205
205
206 $ hg mv tip.bundle top.bundle
206 $ hg mv tip.bundle top.bundle
207 $ hg bundle --base -2 --type v1 top.bundle
207 $ hg bundle --base -2 --type v1 top.bundle
208 1 changesets found
208 1 changesets found
209 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
209 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
210 > y
210 > y
211 > EOF
211 > EOF
212 diff --git a/tip.bundle b/top.bundle
212 diff --git a/tip.bundle b/top.bundle
213 rename from tip.bundle
213 rename from tip.bundle
214 rename to top.bundle
214 rename to top.bundle
215 this modifies a binary file (all or nothing)
215 this modifies a binary file (all or nothing)
216 examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
216 examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
217
217
218
218
219 $ hg tip -p
219 $ hg tip -p
220 changeset: 6:7fa44105f5b3
220 changeset: 6:7fa44105f5b3
221 tag: tip
221 tag: tip
222 user: test
222 user: test
223 date: Thu Jan 01 00:00:06 1970 +0000
223 date: Thu Jan 01 00:00:06 1970 +0000
224 summary: binary-change-rename
224 summary: binary-change-rename
225
225
226 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
226 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
227 Binary file tip.bundle has changed
227 Binary file tip.bundle has changed
228 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
228 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
229 Binary file top.bundle has changed
229 Binary file top.bundle has changed
230
230
231
231
232 Add plain file
232 Add plain file
233
233
234 $ for i in 1 2 3 4 5 6 7 8 9 10; do
234 $ for i in 1 2 3 4 5 6 7 8 9 10; do
235 > echo $i >> plain
235 > echo $i >> plain
236 > done
236 > done
237
237
238 $ hg add plain
238 $ hg add plain
239 $ hg commit -i -d '7 0' -m plain plain<<EOF
239 $ hg commit -i -d '7 0' -m plain plain<<EOF
240 > y
240 > y
241 > y
241 > y
242 > EOF
242 > EOF
243 diff --git a/plain b/plain
243 diff --git a/plain b/plain
244 new file mode 100644
244 new file mode 100644
245 @@ -0,0 +1,10 @@
245 @@ -0,0 +1,10 @@
246 +1
246 +1
247 +2
247 +2
248 +3
248 +3
249 +4
249 +4
250 +5
250 +5
251 +6
251 +6
252 +7
252 +7
253 +8
253 +8
254 +9
254 +9
255 +10
255 +10
256 record this change to 'plain'? [Ynesfdaq?] y
256 record this change to 'plain'? [Ynesfdaq?] y
257
257
258 $ hg tip -p
258 $ hg tip -p
259 changeset: 7:11fb457c1be4
259 changeset: 7:11fb457c1be4
260 tag: tip
260 tag: tip
261 user: test
261 user: test
262 date: Thu Jan 01 00:00:07 1970 +0000
262 date: Thu Jan 01 00:00:07 1970 +0000
263 summary: plain
263 summary: plain
264
264
265 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
265 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
266 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
266 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
267 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
267 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
268 @@ -0,0 +1,10 @@
268 @@ -0,0 +1,10 @@
269 +1
269 +1
270 +2
270 +2
271 +3
271 +3
272 +4
272 +4
273 +5
273 +5
274 +6
274 +6
275 +7
275 +7
276 +8
276 +8
277 +9
277 +9
278 +10
278 +10
279
279
280 Modify end of plain file with username unset
280 Modify end of plain file with username unset
281
281
282 $ echo 11 >> plain
282 $ echo 11 >> plain
283 $ unset HGUSER
283 $ unset HGUSER
284 $ hg commit -i --config ui.username= -d '8 0' -m end plain
284 $ hg commit -i --config ui.username= -d '8 0' -m end plain
285 abort: no username supplied
285 abort: no username supplied
286 (use 'hg config --edit' to set your username)
286 (use 'hg config --edit' to set your username)
287 [255]
287 [255]
288
288
289
289
290 Modify end of plain file, also test that diffopts are accounted for
290 Modify end of plain file, also test that diffopts are accounted for
291
291
292 $ HGUSER="test"
292 $ HGUSER="test"
293 $ export HGUSER
293 $ export HGUSER
294 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
294 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
295 > y
295 > y
296 > y
296 > y
297 > EOF
297 > EOF
298 diff --git a/plain b/plain
298 diff --git a/plain b/plain
299 1 hunks, 1 lines changed
299 1 hunks, 1 lines changed
300 @@ -8,3 +8,4 @@ 7
300 @@ -8,3 +8,4 @@ 7
301 8
301 8
302 9
302 9
303 10
303 10
304 +11
304 +11
305 record this change to 'plain'? [Ynesfdaq?] y
305 record this change to 'plain'? [Ynesfdaq?] y
306
306
307
307
308 Modify end of plain file, no EOL
308 Modify end of plain file, no EOL
309
309
310 $ hg tip --template '{node}' >> plain
310 $ hg tip --template '{node}' >> plain
311 $ hg commit -i -d '9 0' -m noeol plain <<EOF
311 $ hg commit -i -d '9 0' -m noeol plain <<EOF
312 > y
312 > y
313 > y
313 > y
314 > EOF
314 > EOF
315 diff --git a/plain b/plain
315 diff --git a/plain b/plain
316 1 hunks, 1 lines changed
316 1 hunks, 1 lines changed
317 @@ -9,3 +9,4 @@ 8
317 @@ -9,3 +9,4 @@ 8
318 9
318 9
319 10
319 10
320 11
320 11
321 +7264f99c5f5ff3261504828afa4fb4d406c3af54
321 +7264f99c5f5ff3261504828afa4fb4d406c3af54
322 \ No newline at end of file
322 \ No newline at end of file
323 record this change to 'plain'? [Ynesfdaq?] y
323 record this change to 'plain'? [Ynesfdaq?] y
324
324
325
325
326 Record showfunc should preserve function across sections
326 Record showfunc should preserve function across sections
327
327
328 $ cat > f1.py <<NO_CHECK_EOF
328 $ cat > f1.py <<NO_CHECK_EOF
329 > def annotate(ui, repo, *pats, **opts):
329 > def annotate(ui, repo, *pats, **opts):
330 > """show changeset information by line for each file
330 > """show changeset information by line for each file
331 >
331 >
332 > List changes in files, showing the revision id responsible for
332 > List changes in files, showing the revision id responsible for
333 > each line.
333 > each line.
334 >
334 >
335 > This command is useful for discovering when a change was made and
335 > This command is useful for discovering when a change was made and
336 > by whom.
336 > by whom.
337 >
337 >
338 > If you include -f/-u/-d, the revision number is suppressed unless
338 > If you include -f/-u/-d, the revision number is suppressed unless
339 > you also include -the revision number is suppressed unless
339 > you also include -the revision number is suppressed unless
340 > you also include -n.
340 > you also include -n.
341 >
341 >
342 > Without the -a/--text option, annotate will avoid processing files
342 > Without the -a/--text option, annotate will avoid processing files
343 > it detects as binary. With -a, annotate will annotate the file
343 > it detects as binary. With -a, annotate will annotate the file
344 > anyway, although the results will probably be neither useful
344 > anyway, although the results will probably be neither useful
345 > nor desirable.
345 > nor desirable.
346 >
346 >
347 > Returns 0 on success.
347 > Returns 0 on success.
348 > """
348 > """
349 > return 0
349 > return 0
350 > def archive(ui, repo, dest, **opts):
350 > def archive(ui, repo, dest, **opts):
351 > '''create an unversioned archive of a repository revision
351 > '''create an unversioned archive of a repository revision
352 >
352 >
353 > By default, the revision used is the parent of the working
353 > By default, the revision used is the parent of the working
354 > directory; use -r/--rev to specify a different revision.
354 > directory; use -r/--rev to specify a different revision.
355 >
355 >
356 > The archive type is automatically detected based on file
356 > The archive type is automatically detected based on file
357 > extension (to override, use -t/--type).
357 > extension (to override, use -t/--type).
358 >
358 >
359 > .. container:: verbose
359 > .. container:: verbose
360 >
360 >
361 > Valid types are:
361 > Valid types are:
362 > NO_CHECK_EOF
362 > NO_CHECK_EOF
363 $ hg add f1.py
363 $ hg add f1.py
364 $ hg commit -m funcs
364 $ hg commit -m funcs
365 $ cat > f1.py <<NO_CHECK_EOF
365 $ cat > f1.py <<NO_CHECK_EOF
366 > def annotate(ui, repo, *pats, **opts):
366 > def annotate(ui, repo, *pats, **opts):
367 > """show changeset information by line for each file
367 > """show changeset information by line for each file
368 >
368 >
369 > List changes in files, showing the revision id responsible for
369 > List changes in files, showing the revision id responsible for
370 > each line
370 > each line
371 >
371 >
372 > This command is useful for discovering when a change was made and
372 > This command is useful for discovering when a change was made and
373 > by whom.
373 > by whom.
374 >
374 >
375 > Without the -a/--text option, annotate will avoid processing files
375 > Without the -a/--text option, annotate will avoid processing files
376 > it detects as binary. With -a, annotate will annotate the file
376 > it detects as binary. With -a, annotate will annotate the file
377 > anyway, although the results will probably be neither useful
377 > anyway, although the results will probably be neither useful
378 > nor desirable.
378 > nor desirable.
379 >
379 >
380 > Returns 0 on success.
380 > Returns 0 on success.
381 > """
381 > """
382 > return 0
382 > return 0
383 > def archive(ui, repo, dest, **opts):
383 > def archive(ui, repo, dest, **opts):
384 > '''create an unversioned archive of a repository revision
384 > '''create an unversioned archive of a repository revision
385 >
385 >
386 > By default, the revision used is the parent of the working
386 > By default, the revision used is the parent of the working
387 > directory; use -r/--rev to specify a different revision.
387 > directory; use -r/--rev to specify a different revision.
388 >
388 >
389 > The archive type is automatically detected based on file
389 > The archive type is automatically detected based on file
390 > extension (or override using -t/--type).
390 > extension (or override using -t/--type).
391 >
391 >
392 > .. container:: verbose
392 > .. container:: verbose
393 >
393 >
394 > Valid types are:
394 > Valid types are:
395 > NO_CHECK_EOF
395 > NO_CHECK_EOF
396 $ hg commit -i -m interactive <<EOF
396 $ hg commit -i -m interactive <<EOF
397 > y
397 > y
398 > y
398 > y
399 > y
399 > y
400 > y
400 > y
401 > EOF
401 > EOF
402 diff --git a/f1.py b/f1.py
402 diff --git a/f1.py b/f1.py
403 3 hunks, 6 lines changed
403 3 hunks, 6 lines changed
404 examine changes to 'f1.py'? [Ynesfdaq?] y
404 examine changes to 'f1.py'? [Ynesfdaq?] y
405
405
406 @@ -2,8 +2,8 @@ def annotate(ui, repo, *pats, **opts):
406 @@ -2,8 +2,8 @@ def annotate(ui, repo, *pats, **opts):
407 """show changeset information by line for each file
407 """show changeset information by line for each file
408
408
409 List changes in files, showing the revision id responsible for
409 List changes in files, showing the revision id responsible for
410 - each line.
410 - each line.
411 + each line
411 + each line
412
412
413 This command is useful for discovering when a change was made and
413 This command is useful for discovering when a change was made and
414 by whom.
414 by whom.
415
415
416 record change 1/3 to 'f1.py'? [Ynesfdaq?] y
416 record change 1/3 to 'f1.py'? [Ynesfdaq?] y
417
417
418 @@ -6,11 +6,7 @@ def annotate(ui, repo, *pats, **opts):
418 @@ -6,11 +6,7 @@ def annotate(ui, repo, *pats, **opts):
419
419
420 This command is useful for discovering when a change was made and
420 This command is useful for discovering when a change was made and
421 by whom.
421 by whom.
422
422
423 - If you include -f/-u/-d, the revision number is suppressed unless
423 - If you include -f/-u/-d, the revision number is suppressed unless
424 - you also include -the revision number is suppressed unless
424 - you also include -the revision number is suppressed unless
425 - you also include -n.
425 - you also include -n.
426 -
426 -
427 Without the -a/--text option, annotate will avoid processing files
427 Without the -a/--text option, annotate will avoid processing files
428 it detects as binary. With -a, annotate will annotate the file
428 it detects as binary. With -a, annotate will annotate the file
429 anyway, although the results will probably be neither useful
429 anyway, although the results will probably be neither useful
430 record change 2/3 to 'f1.py'? [Ynesfdaq?] y
430 record change 2/3 to 'f1.py'? [Ynesfdaq?] y
431
431
432 @@ -26,7 +22,7 @@ def archive(ui, repo, dest, **opts):
432 @@ -26,7 +22,7 @@ def archive(ui, repo, dest, **opts):
433 directory; use -r/--rev to specify a different revision.
433 directory; use -r/--rev to specify a different revision.
434
434
435 The archive type is automatically detected based on file
435 The archive type is automatically detected based on file
436 - extension (to override, use -t/--type).
436 - extension (to override, use -t/--type).
437 + extension (or override using -t/--type).
437 + extension (or override using -t/--type).
438
438
439 .. container:: verbose
439 .. container:: verbose
440
440
441 record change 3/3 to 'f1.py'? [Ynesfdaq?] y
441 record change 3/3 to 'f1.py'? [Ynesfdaq?] y
442
442
443
443
444 Modify end of plain file, add EOL
444 Modify end of plain file, add EOL
445
445
446 $ echo >> plain
446 $ echo >> plain
447 $ echo 1 > plain2
447 $ echo 1 > plain2
448 $ hg add plain2
448 $ hg add plain2
449 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
449 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
450 > y
450 > y
451 > y
451 > y
452 > y
452 > y
453 > y
453 > y
454 > EOF
454 > EOF
455 diff --git a/plain b/plain
455 diff --git a/plain b/plain
456 1 hunks, 1 lines changed
456 1 hunks, 1 lines changed
457 @@ -9,4 +9,4 @@ 8
457 @@ -9,4 +9,4 @@ 8
458 9
458 9
459 10
459 10
460 11
460 11
461 -7264f99c5f5ff3261504828afa4fb4d406c3af54
461 -7264f99c5f5ff3261504828afa4fb4d406c3af54
462 \ No newline at end of file
462 \ No newline at end of file
463 +7264f99c5f5ff3261504828afa4fb4d406c3af54
463 +7264f99c5f5ff3261504828afa4fb4d406c3af54
464 record change 1/2 to 'plain'? [Ynesfdaq?] y
464 record change 1/2 to 'plain'? [Ynesfdaq?] y
465
465
466 diff --git a/plain2 b/plain2
466 diff --git a/plain2 b/plain2
467 new file mode 100644
467 new file mode 100644
468 @@ -0,0 +1,1 @@
468 @@ -0,0 +1,1 @@
469 +1
469 +1
470 record change 2/2 to 'plain2'? [Ynesfdaq?] y
470 record change 2/2 to 'plain2'? [Ynesfdaq?] y
471
471
472 Modify beginning, trim end, record both, add another file to test
472 Modify beginning, trim end, record both, add another file to test
473 changes numbering
473 changes numbering
474
474
475 $ rm plain
475 $ rm plain
476 $ for i in 2 2 3 4 5 6 7 8 9 10; do
476 $ for i in 2 2 3 4 5 6 7 8 9 10; do
477 > echo $i >> plain
477 > echo $i >> plain
478 > done
478 > done
479 $ echo 2 >> plain2
479 $ echo 2 >> plain2
480
480
481 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
481 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
482 > y
482 > y
483 > y
483 > y
484 > y
484 > y
485 > y
485 > y
486 > y
486 > y
487 > EOF
487 > EOF
488 diff --git a/plain b/plain
488 diff --git a/plain b/plain
489 2 hunks, 3 lines changed
489 2 hunks, 3 lines changed
490 @@ -1,4 +1,4 @@
490 @@ -1,4 +1,4 @@
491 -1
491 -1
492 +2
492 +2
493 2
493 2
494 3
494 3
495 4
495 4
496 record change 1/3 to 'plain'? [Ynesfdaq?] y
496 record change 1/3 to 'plain'? [Ynesfdaq?] y
497
497
498 @@ -8,5 +8,3 @@ 7
498 @@ -8,5 +8,3 @@ 7
499 8
499 8
500 9
500 9
501 10
501 10
502 -11
502 -11
503 -7264f99c5f5ff3261504828afa4fb4d406c3af54
503 -7264f99c5f5ff3261504828afa4fb4d406c3af54
504 record change 2/3 to 'plain'? [Ynesfdaq?] y
504 record change 2/3 to 'plain'? [Ynesfdaq?] y
505
505
506 diff --git a/plain2 b/plain2
506 diff --git a/plain2 b/plain2
507 1 hunks, 1 lines changed
507 1 hunks, 1 lines changed
508 @@ -1,1 +1,2 @@
508 @@ -1,1 +1,2 @@
509 1
509 1
510 +2
510 +2
511 record change 3/3 to 'plain2'? [Ynesfdaq?] y
511 record change 3/3 to 'plain2'? [Ynesfdaq?] y
512
512
513
513
514 $ hg tip -p
514 $ hg tip -p
515 changeset: 13:f941910cff62
515 changeset: 13:f941910cff62
516 tag: tip
516 tag: tip
517 user: test
517 user: test
518 date: Thu Jan 01 00:00:10 1970 +0000
518 date: Thu Jan 01 00:00:10 1970 +0000
519 summary: begin-and-end
519 summary: begin-and-end
520
520
521 diff -r 33abe24d946c -r f941910cff62 plain
521 diff -r 33abe24d946c -r f941910cff62 plain
522 --- a/plain Thu Jan 01 00:00:10 1970 +0000
522 --- a/plain Thu Jan 01 00:00:10 1970 +0000
523 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
523 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
524 @@ -1,4 +1,4 @@
524 @@ -1,4 +1,4 @@
525 -1
525 -1
526 +2
526 +2
527 2
527 2
528 3
528 3
529 4
529 4
530 @@ -8,5 +8,3 @@
530 @@ -8,5 +8,3 @@
531 8
531 8
532 9
532 9
533 10
533 10
534 -11
534 -11
535 -7264f99c5f5ff3261504828afa4fb4d406c3af54
535 -7264f99c5f5ff3261504828afa4fb4d406c3af54
536 diff -r 33abe24d946c -r f941910cff62 plain2
536 diff -r 33abe24d946c -r f941910cff62 plain2
537 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
537 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
538 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
538 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
539 @@ -1,1 +1,2 @@
539 @@ -1,1 +1,2 @@
540 1
540 1
541 +2
541 +2
542
542
543
543
544 Trim beginning, modify end
544 Trim beginning, modify end
545
545
546 $ rm plain
546 $ rm plain
547 > for i in 4 5 6 7 8 9 10.new; do
547 > for i in 4 5 6 7 8 9 10.new; do
548 > echo $i >> plain
548 > echo $i >> plain
549 > done
549 > done
550
550
551 Record end
551 Record end
552
552
553 $ hg commit -i -d '11 0' -m end-only plain <<EOF
553 $ hg commit -i -d '11 0' -m end-only plain <<EOF
554 > n
554 > n
555 > y
555 > y
556 > EOF
556 > EOF
557 diff --git a/plain b/plain
557 diff --git a/plain b/plain
558 2 hunks, 4 lines changed
558 2 hunks, 4 lines changed
559 @@ -1,9 +1,6 @@
559 @@ -1,9 +1,6 @@
560 -2
560 -2
561 -2
561 -2
562 -3
562 -3
563 4
563 4
564 5
564 5
565 6
565 6
566 7
566 7
567 8
567 8
568 9
568 9
569 record change 1/2 to 'plain'? [Ynesfdaq?] n
569 record change 1/2 to 'plain'? [Ynesfdaq?] n
570
570
571 @@ -4,7 +1,7 @@
571 @@ -4,7 +1,7 @@
572 4
572 4
573 5
573 5
574 6
574 6
575 7
575 7
576 8
576 8
577 9
577 9
578 -10
578 -10
579 +10.new
579 +10.new
580 record change 2/2 to 'plain'? [Ynesfdaq?] y
580 record change 2/2 to 'plain'? [Ynesfdaq?] y
581
581
582
582
583 $ hg tip -p
583 $ hg tip -p
584 changeset: 14:4915f538659b
584 changeset: 14:4915f538659b
585 tag: tip
585 tag: tip
586 user: test
586 user: test
587 date: Thu Jan 01 00:00:11 1970 +0000
587 date: Thu Jan 01 00:00:11 1970 +0000
588 summary: end-only
588 summary: end-only
589
589
590 diff -r f941910cff62 -r 4915f538659b plain
590 diff -r f941910cff62 -r 4915f538659b plain
591 --- a/plain Thu Jan 01 00:00:10 1970 +0000
591 --- a/plain Thu Jan 01 00:00:10 1970 +0000
592 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
592 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
593 @@ -7,4 +7,4 @@
593 @@ -7,4 +7,4 @@
594 7
594 7
595 8
595 8
596 9
596 9
597 -10
597 -10
598 +10.new
598 +10.new
599
599
600
600
601 Record beginning
601 Record beginning
602
602
603 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
603 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
604 > y
604 > y
605 > y
605 > y
606 > EOF
606 > EOF
607 diff --git a/plain b/plain
607 diff --git a/plain b/plain
608 1 hunks, 3 lines changed
608 1 hunks, 3 lines changed
609 @@ -1,6 +1,3 @@
609 @@ -1,6 +1,3 @@
610 -2
610 -2
611 -2
611 -2
612 -3
612 -3
613 4
613 4
614 5
614 5
615 6
615 6
616 record this change to 'plain'? [Ynesfdaq?] y
616 record this change to 'plain'? [Ynesfdaq?] y
617
617
618
618
619 $ hg tip -p
619 $ hg tip -p
620 changeset: 15:1b1f93d4b94b
620 changeset: 15:1b1f93d4b94b
621 tag: tip
621 tag: tip
622 user: test
622 user: test
623 date: Thu Jan 01 00:00:12 1970 +0000
623 date: Thu Jan 01 00:00:12 1970 +0000
624 summary: begin-only
624 summary: begin-only
625
625
626 diff -r 4915f538659b -r 1b1f93d4b94b plain
626 diff -r 4915f538659b -r 1b1f93d4b94b plain
627 --- a/plain Thu Jan 01 00:00:11 1970 +0000
627 --- a/plain Thu Jan 01 00:00:11 1970 +0000
628 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
628 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
629 @@ -1,6 +1,3 @@
629 @@ -1,6 +1,3 @@
630 -2
630 -2
631 -2
631 -2
632 -3
632 -3
633 4
633 4
634 5
634 5
635 6
635 6
636
636
637
637
638 Add to beginning, trim from end
638 Add to beginning, trim from end
639
639
640 $ rm plain
640 $ rm plain
641 $ for i in 1 2 3 4 5 6 7 8 9; do
641 $ for i in 1 2 3 4 5 6 7 8 9; do
642 > echo $i >> plain
642 > echo $i >> plain
643 > done
643 > done
644
644
645 Record end
645 Record end
646
646
647 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
647 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
648 > n
648 > n
649 > y
649 > y
650 > EOF
650 > EOF
651 diff --git a/plain b/plain
651 diff --git a/plain b/plain
652 2 hunks, 4 lines changed
652 2 hunks, 4 lines changed
653 @@ -1,6 +1,9 @@
653 @@ -1,6 +1,9 @@
654 +1
654 +1
655 +2
655 +2
656 +3
656 +3
657 4
657 4
658 5
658 5
659 6
659 6
660 7
660 7
661 8
661 8
662 9
662 9
663 record change 1/2 to 'plain'? [Ynesfdaq?] n
663 record change 1/2 to 'plain'? [Ynesfdaq?] n
664
664
665 @@ -1,7 +4,6 @@
665 @@ -1,7 +4,6 @@
666 4
666 4
667 5
667 5
668 6
668 6
669 7
669 7
670 8
670 8
671 9
671 9
672 -10.new
672 -10.new
673 record change 2/2 to 'plain'? [Ynesfdaq?] y
673 record change 2/2 to 'plain'? [Ynesfdaq?] y
674
674
675
675
676 Add to beginning, middle, end
676 Add to beginning, middle, end
677
677
678 $ rm plain
678 $ rm plain
679 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
679 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
680 > echo $i >> plain
680 > echo $i >> plain
681 > done
681 > done
682
682
683 Record beginning, middle, and test that format-breaking diffopts are ignored
683 Record beginning, middle, and test that format-breaking diffopts are ignored
684
684
685 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
685 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
686 > y
686 > y
687 > y
687 > y
688 > n
688 > n
689 > EOF
689 > EOF
690 diff --git a/plain b/plain
690 diff --git a/plain b/plain
691 3 hunks, 7 lines changed
691 3 hunks, 7 lines changed
692 @@ -1,2 +1,5 @@
692 @@ -1,2 +1,5 @@
693 +1
693 +1
694 +2
694 +2
695 +3
695 +3
696 4
696 4
697 5
697 5
698 record change 1/3 to 'plain'? [Ynesfdaq?] y
698 record change 1/3 to 'plain'? [Ynesfdaq?] y
699
699
700 @@ -1,6 +4,8 @@
700 @@ -1,6 +4,8 @@
701 4
701 4
702 5
702 5
703 +5.new
703 +5.new
704 +5.reallynew
704 +5.reallynew
705 6
705 6
706 7
706 7
707 8
707 8
708 9
708 9
709 record change 2/3 to 'plain'? [Ynesfdaq?] y
709 record change 2/3 to 'plain'? [Ynesfdaq?] y
710
710
711 @@ -3,4 +8,6 @@
711 @@ -3,4 +8,6 @@
712 6
712 6
713 7
713 7
714 8
714 8
715 9
715 9
716 +10
716 +10
717 +11
717 +11
718 record change 3/3 to 'plain'? [Ynesfdaq?] n
718 record change 3/3 to 'plain'? [Ynesfdaq?] n
719
719
720
720
721 $ hg tip -p
721 $ hg tip -p
722 changeset: 17:41cf3f5c55ae
722 changeset: 17:41cf3f5c55ae
723 tag: tip
723 tag: tip
724 user: test
724 user: test
725 date: Thu Jan 01 00:00:14 1970 +0000
725 date: Thu Jan 01 00:00:14 1970 +0000
726 summary: middle-only
726 summary: middle-only
727
727
728 diff -r a69d252246e1 -r 41cf3f5c55ae plain
728 diff -r a69d252246e1 -r 41cf3f5c55ae plain
729 --- a/plain Thu Jan 01 00:00:13 1970 +0000
729 --- a/plain Thu Jan 01 00:00:13 1970 +0000
730 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
730 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
731 @@ -1,5 +1,10 @@
731 @@ -1,5 +1,10 @@
732 +1
732 +1
733 +2
733 +2
734 +3
734 +3
735 4
735 4
736 5
736 5
737 +5.new
737 +5.new
738 +5.reallynew
738 +5.reallynew
739 6
739 6
740 7
740 7
741 8
741 8
742
742
743
743
744 Record end
744 Record end
745
745
746 $ hg commit -i -d '15 0' -m end-only plain <<EOF
746 $ hg commit -i -d '15 0' -m end-only plain <<EOF
747 > y
747 > y
748 > y
748 > y
749 > EOF
749 > EOF
750 diff --git a/plain b/plain
750 diff --git a/plain b/plain
751 1 hunks, 2 lines changed
751 1 hunks, 2 lines changed
752 @@ -9,3 +9,5 @@ 6
752 @@ -9,3 +9,5 @@ 6
753 7
753 7
754 8
754 8
755 9
755 9
756 +10
756 +10
757 +11
757 +11
758 record this change to 'plain'? [Ynesfdaq?] y
758 record this change to 'plain'? [Ynesfdaq?] y
759
759
760
760
761 $ hg tip -p
761 $ hg tip -p
762 changeset: 18:58a72f46bc24
762 changeset: 18:58a72f46bc24
763 tag: tip
763 tag: tip
764 user: test
764 user: test
765 date: Thu Jan 01 00:00:15 1970 +0000
765 date: Thu Jan 01 00:00:15 1970 +0000
766 summary: end-only
766 summary: end-only
767
767
768 diff -r 41cf3f5c55ae -r 58a72f46bc24 plain
768 diff -r 41cf3f5c55ae -r 58a72f46bc24 plain
769 --- a/plain Thu Jan 01 00:00:14 1970 +0000
769 --- a/plain Thu Jan 01 00:00:14 1970 +0000
770 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
770 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
771 @@ -9,3 +9,5 @@
771 @@ -9,3 +9,5 @@
772 7
772 7
773 8
773 8
774 9
774 9
775 +10
775 +10
776 +11
776 +11
777
777
778 Interactive commit can name a directory instead of files (issue6131)
778
779
779 $ mkdir subdir
780 $ mkdir subdir
781 $ echo a > subdir/a
782 $ hg ci -d '16 0' -i subdir -Amsubdir <<EOF
783 > y
784 > y
785 > EOF
786 adding subdir/a
787 diff --git a/subdir/a b/subdir/a
788 new file mode 100644
789 examine changes to 'subdir/a'? [Ynesfdaq?] y
790
791 @@ -0,0 +1,1 @@
792 +a
793 record this change to 'subdir/a'? [Ynesfdaq?] y
794
780 $ cd subdir
795 $ cd subdir
781 $ echo a > a
782 $ hg ci -d '16 0' -Amsubdir
783 adding subdir/a
784
796
785 $ echo a >> a
797 $ echo a >> a
786 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
798 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
787 > y
799 > y
788 > y
800 > y
789 > EOF
801 > EOF
790 diff --git a/subdir/a b/subdir/a
802 diff --git a/subdir/a b/subdir/a
791 1 hunks, 1 lines changed
803 1 hunks, 1 lines changed
792 @@ -1,1 +1,2 @@
804 @@ -1,1 +1,2 @@
793 a
805 a
794 +a
806 +a
795 record this change to 'subdir/a'? [Ynesfdaq?] y
807 record this change to 'subdir/a'? [Ynesfdaq?] y
796
808
797
809
798 $ hg tip -p
810 $ hg tip -p
799 changeset: 20:e0f6b99f6c49
811 changeset: 20:e0f6b99f6c49
800 tag: tip
812 tag: tip
801 user: test
813 user: test
802 date: Thu Jan 01 00:00:16 1970 +0000
814 date: Thu Jan 01 00:00:16 1970 +0000
803 summary: subdir-change
815 summary: subdir-change
804
816
805 diff -r abd26b51de37 -r e0f6b99f6c49 subdir/a
817 diff -r abd26b51de37 -r e0f6b99f6c49 subdir/a
806 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
818 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
807 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
819 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
808 @@ -1,1 +1,2 @@
820 @@ -1,1 +1,2 @@
809 a
821 a
810 +a
822 +a
811
823
812
824
813 $ echo a > f1
825 $ echo a > f1
814 $ echo b > f2
826 $ echo b > f2
815 $ hg add f1 f2
827 $ hg add f1 f2
816
828
817 $ hg ci -mz -d '17 0'
829 $ hg ci -mz -d '17 0'
818
830
819 $ echo a >> f1
831 $ echo a >> f1
820 $ echo b >> f2
832 $ echo b >> f2
821
833
822 Help, quit
834 Help, quit
823
835
824 $ hg commit -i <<EOF
836 $ hg commit -i <<EOF
825 > ?
837 > ?
826 > q
838 > q
827 > EOF
839 > EOF
828 diff --git a/subdir/f1 b/subdir/f1
840 diff --git a/subdir/f1 b/subdir/f1
829 1 hunks, 1 lines changed
841 1 hunks, 1 lines changed
830 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
842 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
831
843
832 y - yes, record this change
844 y - yes, record this change
833 n - no, skip this change
845 n - no, skip this change
834 e - edit this change manually
846 e - edit this change manually
835 s - skip remaining changes to this file
847 s - skip remaining changes to this file
836 f - record remaining changes to this file
848 f - record remaining changes to this file
837 d - done, skip remaining changes and files
849 d - done, skip remaining changes and files
838 a - record all changes to all remaining files
850 a - record all changes to all remaining files
839 q - quit, recording no changes
851 q - quit, recording no changes
840 ? - ? (display help)
852 ? - ? (display help)
841 examine changes to 'subdir/f1'? [Ynesfdaq?] q
853 examine changes to 'subdir/f1'? [Ynesfdaq?] q
842
854
843 abort: user quit
855 abort: user quit
844 [255]
856 [255]
845
857
846 Patterns
858 Patterns
847
859
848 $ hg commit -i 'glob:f*' << EOF
860 $ hg commit -i 'glob:f*' << EOF
849 > y
861 > y
850 > n
862 > n
851 > y
863 > y
852 > n
864 > n
853 > EOF
865 > EOF
854 diff --git a/subdir/f1 b/subdir/f1
866 diff --git a/subdir/f1 b/subdir/f1
855 1 hunks, 1 lines changed
867 1 hunks, 1 lines changed
856 examine changes to 'subdir/f1'? [Ynesfdaq?] y
868 examine changes to 'subdir/f1'? [Ynesfdaq?] y
857
869
858 @@ -1,1 +1,2 @@
870 @@ -1,1 +1,2 @@
859 a
871 a
860 +a
872 +a
861 record change 1/2 to 'subdir/f1'? [Ynesfdaq?] n
873 record change 1/2 to 'subdir/f1'? [Ynesfdaq?] n
862
874
863 diff --git a/subdir/f2 b/subdir/f2
875 diff --git a/subdir/f2 b/subdir/f2
864 1 hunks, 1 lines changed
876 1 hunks, 1 lines changed
865 examine changes to 'subdir/f2'? [Ynesfdaq?] y
877 examine changes to 'subdir/f2'? [Ynesfdaq?] y
866
878
867 @@ -1,1 +1,2 @@
879 @@ -1,1 +1,2 @@
868 b
880 b
869 +b
881 +b
870 record change 2/2 to 'subdir/f2'? [Ynesfdaq?] n
882 record change 2/2 to 'subdir/f2'? [Ynesfdaq?] n
871
883
872 no changes to record
884 no changes to record
873 [1]
885 [1]
874
886
875 #if gettext
887 #if gettext
876
888
877 Test translated help message
889 Test translated help message
878
890
879 str.lower() instead of encoding.lower(str) on translated message might
891 str.lower() instead of encoding.lower(str) on translated message might
880 make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z)
892 make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z)
881 as the second or later byte of multi-byte character.
893 as the second or later byte of multi-byte character.
882
894
883 For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932)
895 For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932)
884 contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this
896 contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this
885 replacement makes message meaningless.
897 replacement makes message meaningless.
886
898
887 This tests that translated help message is lower()-ed correctly.
899 This tests that translated help message is lower()-ed correctly.
888
900
889 $ LANGUAGE=ja
901 $ LANGUAGE=ja
890 $ export LANGUAGE
902 $ export LANGUAGE
891
903
892 $ cat > $TESTTMP/escape.py <<EOF
904 $ cat > $TESTTMP/escape.py <<EOF
893 > from __future__ import absolute_import
905 > from __future__ import absolute_import
894 > from mercurial import (
906 > from mercurial import (
895 > pycompat,
907 > pycompat,
896 > )
908 > )
897 > from mercurial.utils import (
909 > from mercurial.utils import (
898 > procutil,
910 > procutil,
899 > )
911 > )
900 > def escape(c):
912 > def escape(c):
901 > o = ord(c)
913 > o = ord(c)
902 > if o < 0x80:
914 > if o < 0x80:
903 > return c
915 > return c
904 > else:
916 > else:
905 > return br'\x%02x' % o # escape char setting MSB
917 > return br'\x%02x' % o # escape char setting MSB
906 > for l in procutil.stdin:
918 > for l in procutil.stdin:
907 > procutil.stdout.write(
919 > procutil.stdout.write(
908 > b''.join(escape(c) for c in pycompat.iterbytestr(l)))
920 > b''.join(escape(c) for c in pycompat.iterbytestr(l)))
909 > EOF
921 > EOF
910
922
911 $ hg commit -i --encoding cp932 2>&1 <<EOF | "$PYTHON" $TESTTMP/escape.py | grep '^y - '
923 $ hg commit -i --encoding cp932 2>&1 <<EOF | "$PYTHON" $TESTTMP/escape.py | grep '^y - '
912 > ?
924 > ?
913 > q
925 > q
914 > EOF
926 > EOF
915 y - \x82\xb1\x82\xcc\x95\xcf\x8dX\x82\xf0\x8bL\x98^(yes)
927 y - \x82\xb1\x82\xcc\x95\xcf\x8dX\x82\xf0\x8bL\x98^(yes)
916
928
917 $ LANGUAGE=
929 $ LANGUAGE=
918 #endif
930 #endif
919
931
920 Skip
932 Skip
921
933
922 $ hg commit -i <<EOF
934 $ hg commit -i <<EOF
923 > s
935 > s
924 > EOF
936 > EOF
925 diff --git a/subdir/f1 b/subdir/f1
937 diff --git a/subdir/f1 b/subdir/f1
926 1 hunks, 1 lines changed
938 1 hunks, 1 lines changed
927 examine changes to 'subdir/f1'? [Ynesfdaq?] s
939 examine changes to 'subdir/f1'? [Ynesfdaq?] s
928
940
929 diff --git a/subdir/f2 b/subdir/f2
941 diff --git a/subdir/f2 b/subdir/f2
930 1 hunks, 1 lines changed
942 1 hunks, 1 lines changed
931 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
943 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
932 [255]
944 [255]
933
945
934 No
946 No
935
947
936 $ hg commit -i <<EOF
948 $ hg commit -i <<EOF
937 > n
949 > n
938 > EOF
950 > EOF
939 diff --git a/subdir/f1 b/subdir/f1
951 diff --git a/subdir/f1 b/subdir/f1
940 1 hunks, 1 lines changed
952 1 hunks, 1 lines changed
941 examine changes to 'subdir/f1'? [Ynesfdaq?] n
953 examine changes to 'subdir/f1'? [Ynesfdaq?] n
942
954
943 diff --git a/subdir/f2 b/subdir/f2
955 diff --git a/subdir/f2 b/subdir/f2
944 1 hunks, 1 lines changed
956 1 hunks, 1 lines changed
945 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
957 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
946 [255]
958 [255]
947
959
948 f, quit
960 f, quit
949
961
950 $ hg commit -i <<EOF
962 $ hg commit -i <<EOF
951 > f
963 > f
952 > q
964 > q
953 > EOF
965 > EOF
954 diff --git a/subdir/f1 b/subdir/f1
966 diff --git a/subdir/f1 b/subdir/f1
955 1 hunks, 1 lines changed
967 1 hunks, 1 lines changed
956 examine changes to 'subdir/f1'? [Ynesfdaq?] f
968 examine changes to 'subdir/f1'? [Ynesfdaq?] f
957
969
958 diff --git a/subdir/f2 b/subdir/f2
970 diff --git a/subdir/f2 b/subdir/f2
959 1 hunks, 1 lines changed
971 1 hunks, 1 lines changed
960 examine changes to 'subdir/f2'? [Ynesfdaq?] q
972 examine changes to 'subdir/f2'? [Ynesfdaq?] q
961
973
962 abort: user quit
974 abort: user quit
963 [255]
975 [255]
964
976
965 s, all
977 s, all
966
978
967 $ hg commit -i -d '18 0' -mx <<EOF
979 $ hg commit -i -d '18 0' -mx <<EOF
968 > s
980 > s
969 > a
981 > a
970 > EOF
982 > EOF
971 diff --git a/subdir/f1 b/subdir/f1
983 diff --git a/subdir/f1 b/subdir/f1
972 1 hunks, 1 lines changed
984 1 hunks, 1 lines changed
973 examine changes to 'subdir/f1'? [Ynesfdaq?] s
985 examine changes to 'subdir/f1'? [Ynesfdaq?] s
974
986
975 diff --git a/subdir/f2 b/subdir/f2
987 diff --git a/subdir/f2 b/subdir/f2
976 1 hunks, 1 lines changed
988 1 hunks, 1 lines changed
977 examine changes to 'subdir/f2'? [Ynesfdaq?] a
989 examine changes to 'subdir/f2'? [Ynesfdaq?] a
978
990
979
991
980 $ hg tip -p
992 $ hg tip -p
981 changeset: 22:6afbbefacf35
993 changeset: 22:6afbbefacf35
982 tag: tip
994 tag: tip
983 user: test
995 user: test
984 date: Thu Jan 01 00:00:18 1970 +0000
996 date: Thu Jan 01 00:00:18 1970 +0000
985 summary: x
997 summary: x
986
998
987 diff -r b73c401c693c -r 6afbbefacf35 subdir/f2
999 diff -r b73c401c693c -r 6afbbefacf35 subdir/f2
988 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
1000 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
989 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
1001 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
990 @@ -1,1 +1,2 @@
1002 @@ -1,1 +1,2 @@
991 b
1003 b
992 +b
1004 +b
993
1005
994
1006
995 f
1007 f
996
1008
997 $ hg commit -i -d '19 0' -my <<EOF
1009 $ hg commit -i -d '19 0' -my <<EOF
998 > f
1010 > f
999 > EOF
1011 > EOF
1000 diff --git a/subdir/f1 b/subdir/f1
1012 diff --git a/subdir/f1 b/subdir/f1
1001 1 hunks, 1 lines changed
1013 1 hunks, 1 lines changed
1002 examine changes to 'subdir/f1'? [Ynesfdaq?] f
1014 examine changes to 'subdir/f1'? [Ynesfdaq?] f
1003
1015
1004
1016
1005 $ hg tip -p
1017 $ hg tip -p
1006 changeset: 23:715028a33949
1018 changeset: 23:715028a33949
1007 tag: tip
1019 tag: tip
1008 user: test
1020 user: test
1009 date: Thu Jan 01 00:00:19 1970 +0000
1021 date: Thu Jan 01 00:00:19 1970 +0000
1010 summary: y
1022 summary: y
1011
1023
1012 diff -r 6afbbefacf35 -r 715028a33949 subdir/f1
1024 diff -r 6afbbefacf35 -r 715028a33949 subdir/f1
1013 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
1025 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
1014 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
1026 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
1015 @@ -1,1 +1,2 @@
1027 @@ -1,1 +1,2 @@
1016 a
1028 a
1017 +a
1029 +a
1018
1030
1019
1031
1020 #if execbit
1032 #if execbit
1021
1033
1022 Preserve chmod +x
1034 Preserve chmod +x
1023
1035
1024 $ chmod +x f1
1036 $ chmod +x f1
1025 $ echo a >> f1
1037 $ echo a >> f1
1026 $ hg commit -i -d '20 0' -mz <<EOF
1038 $ hg commit -i -d '20 0' -mz <<EOF
1027 > y
1039 > y
1028 > y
1040 > y
1029 > y
1041 > y
1030 > EOF
1042 > EOF
1031 diff --git a/subdir/f1 b/subdir/f1
1043 diff --git a/subdir/f1 b/subdir/f1
1032 old mode 100644
1044 old mode 100644
1033 new mode 100755
1045 new mode 100755
1034 1 hunks, 1 lines changed
1046 1 hunks, 1 lines changed
1035 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1047 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1036
1048
1037 @@ -1,2 +1,3 @@
1049 @@ -1,2 +1,3 @@
1038 a
1050 a
1039 a
1051 a
1040 +a
1052 +a
1041 record this change to 'subdir/f1'? [Ynesfdaq?] y
1053 record this change to 'subdir/f1'? [Ynesfdaq?] y
1042
1054
1043
1055
1044 $ hg tip --config diff.git=True -p
1056 $ hg tip --config diff.git=True -p
1045 changeset: 24:db967c1e5884
1057 changeset: 24:db967c1e5884
1046 tag: tip
1058 tag: tip
1047 user: test
1059 user: test
1048 date: Thu Jan 01 00:00:20 1970 +0000
1060 date: Thu Jan 01 00:00:20 1970 +0000
1049 summary: z
1061 summary: z
1050
1062
1051 diff --git a/subdir/f1 b/subdir/f1
1063 diff --git a/subdir/f1 b/subdir/f1
1052 old mode 100644
1064 old mode 100644
1053 new mode 100755
1065 new mode 100755
1054 --- a/subdir/f1
1066 --- a/subdir/f1
1055 +++ b/subdir/f1
1067 +++ b/subdir/f1
1056 @@ -1,2 +1,3 @@
1068 @@ -1,2 +1,3 @@
1057 a
1069 a
1058 a
1070 a
1059 +a
1071 +a
1060
1072
1061
1073
1062 Preserve execute permission on original
1074 Preserve execute permission on original
1063
1075
1064 $ echo b >> f1
1076 $ echo b >> f1
1065 $ hg commit -i -d '21 0' -maa <<EOF
1077 $ hg commit -i -d '21 0' -maa <<EOF
1066 > y
1078 > y
1067 > y
1079 > y
1068 > y
1080 > y
1069 > EOF
1081 > EOF
1070 diff --git a/subdir/f1 b/subdir/f1
1082 diff --git a/subdir/f1 b/subdir/f1
1071 1 hunks, 1 lines changed
1083 1 hunks, 1 lines changed
1072 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1084 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1073
1085
1074 @@ -1,3 +1,4 @@
1086 @@ -1,3 +1,4 @@
1075 a
1087 a
1076 a
1088 a
1077 a
1089 a
1078 +b
1090 +b
1079 record this change to 'subdir/f1'? [Ynesfdaq?] y
1091 record this change to 'subdir/f1'? [Ynesfdaq?] y
1080
1092
1081
1093
1082 $ hg tip --config diff.git=True -p
1094 $ hg tip --config diff.git=True -p
1083 changeset: 25:88903aef81c3
1095 changeset: 25:88903aef81c3
1084 tag: tip
1096 tag: tip
1085 user: test
1097 user: test
1086 date: Thu Jan 01 00:00:21 1970 +0000
1098 date: Thu Jan 01 00:00:21 1970 +0000
1087 summary: aa
1099 summary: aa
1088
1100
1089 diff --git a/subdir/f1 b/subdir/f1
1101 diff --git a/subdir/f1 b/subdir/f1
1090 --- a/subdir/f1
1102 --- a/subdir/f1
1091 +++ b/subdir/f1
1103 +++ b/subdir/f1
1092 @@ -1,3 +1,4 @@
1104 @@ -1,3 +1,4 @@
1093 a
1105 a
1094 a
1106 a
1095 a
1107 a
1096 +b
1108 +b
1097
1109
1098
1110
1099 Preserve chmod -x
1111 Preserve chmod -x
1100
1112
1101 $ chmod -x f1
1113 $ chmod -x f1
1102 $ echo c >> f1
1114 $ echo c >> f1
1103 $ hg commit -i -d '22 0' -mab <<EOF
1115 $ hg commit -i -d '22 0' -mab <<EOF
1104 > y
1116 > y
1105 > y
1117 > y
1106 > y
1118 > y
1107 > EOF
1119 > EOF
1108 diff --git a/subdir/f1 b/subdir/f1
1120 diff --git a/subdir/f1 b/subdir/f1
1109 old mode 100755
1121 old mode 100755
1110 new mode 100644
1122 new mode 100644
1111 1 hunks, 1 lines changed
1123 1 hunks, 1 lines changed
1112 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1124 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1113
1125
1114 @@ -2,3 +2,4 @@ a
1126 @@ -2,3 +2,4 @@ a
1115 a
1127 a
1116 a
1128 a
1117 b
1129 b
1118 +c
1130 +c
1119 record this change to 'subdir/f1'? [Ynesfdaq?] y
1131 record this change to 'subdir/f1'? [Ynesfdaq?] y
1120
1132
1121
1133
1122 $ hg tip --config diff.git=True -p
1134 $ hg tip --config diff.git=True -p
1123 changeset: 26:7af84b6cf560
1135 changeset: 26:7af84b6cf560
1124 tag: tip
1136 tag: tip
1125 user: test
1137 user: test
1126 date: Thu Jan 01 00:00:22 1970 +0000
1138 date: Thu Jan 01 00:00:22 1970 +0000
1127 summary: ab
1139 summary: ab
1128
1140
1129 diff --git a/subdir/f1 b/subdir/f1
1141 diff --git a/subdir/f1 b/subdir/f1
1130 old mode 100755
1142 old mode 100755
1131 new mode 100644
1143 new mode 100644
1132 --- a/subdir/f1
1144 --- a/subdir/f1
1133 +++ b/subdir/f1
1145 +++ b/subdir/f1
1134 @@ -2,3 +2,4 @@
1146 @@ -2,3 +2,4 @@
1135 a
1147 a
1136 a
1148 a
1137 b
1149 b
1138 +c
1150 +c
1139
1151
1140
1152
1141 #else
1153 #else
1142
1154
1143 Slightly bogus tests to get almost same repo structure as when x bit is used
1155 Slightly bogus tests to get almost same repo structure as when x bit is used
1144 - but with different hashes.
1156 - but with different hashes.
1145
1157
1146 Mock "Preserve chmod +x"
1158 Mock "Preserve chmod +x"
1147
1159
1148 $ echo a >> f1
1160 $ echo a >> f1
1149 $ hg commit -i -d '20 0' -mz <<EOF
1161 $ hg commit -i -d '20 0' -mz <<EOF
1150 > y
1162 > y
1151 > y
1163 > y
1152 > y
1164 > y
1153 > EOF
1165 > EOF
1154 diff --git a/subdir/f1 b/subdir/f1
1166 diff --git a/subdir/f1 b/subdir/f1
1155 1 hunks, 1 lines changed
1167 1 hunks, 1 lines changed
1156 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1168 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1157
1169
1158 @@ -1,2 +1,3 @@
1170 @@ -1,2 +1,3 @@
1159 a
1171 a
1160 a
1172 a
1161 +a
1173 +a
1162 record this change to 'subdir/f1'? [Ynesfdaq?] y
1174 record this change to 'subdir/f1'? [Ynesfdaq?] y
1163
1175
1164
1176
1165 $ hg tip --config diff.git=True -p
1177 $ hg tip --config diff.git=True -p
1166 changeset: 24:c26cfe2c4eb0
1178 changeset: 24:c26cfe2c4eb0
1167 tag: tip
1179 tag: tip
1168 user: test
1180 user: test
1169 date: Thu Jan 01 00:00:20 1970 +0000
1181 date: Thu Jan 01 00:00:20 1970 +0000
1170 summary: z
1182 summary: z
1171
1183
1172 diff --git a/subdir/f1 b/subdir/f1
1184 diff --git a/subdir/f1 b/subdir/f1
1173 --- a/subdir/f1
1185 --- a/subdir/f1
1174 +++ b/subdir/f1
1186 +++ b/subdir/f1
1175 @@ -1,2 +1,3 @@
1187 @@ -1,2 +1,3 @@
1176 a
1188 a
1177 a
1189 a
1178 +a
1190 +a
1179
1191
1180
1192
1181 Mock "Preserve execute permission on original"
1193 Mock "Preserve execute permission on original"
1182
1194
1183 $ echo b >> f1
1195 $ echo b >> f1
1184 $ hg commit -i -d '21 0' -maa <<EOF
1196 $ hg commit -i -d '21 0' -maa <<EOF
1185 > y
1197 > y
1186 > y
1198 > y
1187 > y
1199 > y
1188 > EOF
1200 > EOF
1189 diff --git a/subdir/f1 b/subdir/f1
1201 diff --git a/subdir/f1 b/subdir/f1
1190 1 hunks, 1 lines changed
1202 1 hunks, 1 lines changed
1191 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1203 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1192
1204
1193 @@ -1,3 +1,4 @@
1205 @@ -1,3 +1,4 @@
1194 a
1206 a
1195 a
1207 a
1196 a
1208 a
1197 +b
1209 +b
1198 record this change to 'subdir/f1'? [Ynesfdaq?] y
1210 record this change to 'subdir/f1'? [Ynesfdaq?] y
1199
1211
1200
1212
1201 $ hg tip --config diff.git=True -p
1213 $ hg tip --config diff.git=True -p
1202 changeset: 25:a48d2d60adde
1214 changeset: 25:a48d2d60adde
1203 tag: tip
1215 tag: tip
1204 user: test
1216 user: test
1205 date: Thu Jan 01 00:00:21 1970 +0000
1217 date: Thu Jan 01 00:00:21 1970 +0000
1206 summary: aa
1218 summary: aa
1207
1219
1208 diff --git a/subdir/f1 b/subdir/f1
1220 diff --git a/subdir/f1 b/subdir/f1
1209 --- a/subdir/f1
1221 --- a/subdir/f1
1210 +++ b/subdir/f1
1222 +++ b/subdir/f1
1211 @@ -1,3 +1,4 @@
1223 @@ -1,3 +1,4 @@
1212 a
1224 a
1213 a
1225 a
1214 a
1226 a
1215 +b
1227 +b
1216
1228
1217
1229
1218 Mock "Preserve chmod -x"
1230 Mock "Preserve chmod -x"
1219
1231
1220 $ chmod -x f1
1232 $ chmod -x f1
1221 $ echo c >> f1
1233 $ echo c >> f1
1222 $ hg commit -i -d '22 0' -mab <<EOF
1234 $ hg commit -i -d '22 0' -mab <<EOF
1223 > y
1235 > y
1224 > y
1236 > y
1225 > y
1237 > y
1226 > EOF
1238 > EOF
1227 diff --git a/subdir/f1 b/subdir/f1
1239 diff --git a/subdir/f1 b/subdir/f1
1228 1 hunks, 1 lines changed
1240 1 hunks, 1 lines changed
1229 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1241 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1230
1242
1231 @@ -2,3 +2,4 @@ a
1243 @@ -2,3 +2,4 @@ a
1232 a
1244 a
1233 a
1245 a
1234 b
1246 b
1235 +c
1247 +c
1236 record this change to 'subdir/f1'? [Ynesfdaq?] y
1248 record this change to 'subdir/f1'? [Ynesfdaq?] y
1237
1249
1238
1250
1239 $ hg tip --config diff.git=True -p
1251 $ hg tip --config diff.git=True -p
1240 changeset: 26:5cc89ae210fa
1252 changeset: 26:5cc89ae210fa
1241 tag: tip
1253 tag: tip
1242 user: test
1254 user: test
1243 date: Thu Jan 01 00:00:22 1970 +0000
1255 date: Thu Jan 01 00:00:22 1970 +0000
1244 summary: ab
1256 summary: ab
1245
1257
1246 diff --git a/subdir/f1 b/subdir/f1
1258 diff --git a/subdir/f1 b/subdir/f1
1247 --- a/subdir/f1
1259 --- a/subdir/f1
1248 +++ b/subdir/f1
1260 +++ b/subdir/f1
1249 @@ -2,3 +2,4 @@
1261 @@ -2,3 +2,4 @@
1250 a
1262 a
1251 a
1263 a
1252 b
1264 b
1253 +c
1265 +c
1254
1266
1255
1267
1256 #endif
1268 #endif
1257
1269
1258 $ cd ..
1270 $ cd ..
1259
1271
1260
1272
1261 Abort early when a merge is in progress
1273 Abort early when a merge is in progress
1262
1274
1263 $ hg up 4
1275 $ hg up 4
1264 1 files updated, 0 files merged, 7 files removed, 0 files unresolved
1276 1 files updated, 0 files merged, 7 files removed, 0 files unresolved
1265
1277
1266 $ touch iwillmergethat
1278 $ touch iwillmergethat
1267 $ hg add iwillmergethat
1279 $ hg add iwillmergethat
1268
1280
1269 $ hg branch thatbranch
1281 $ hg branch thatbranch
1270 marked working directory as branch thatbranch
1282 marked working directory as branch thatbranch
1271 (branches are permanent and global, did you want a bookmark?)
1283 (branches are permanent and global, did you want a bookmark?)
1272
1284
1273 $ hg ci -m'new head'
1285 $ hg ci -m'new head'
1274
1286
1275 $ hg up default
1287 $ hg up default
1276 7 files updated, 0 files merged, 2 files removed, 0 files unresolved
1288 7 files updated, 0 files merged, 2 files removed, 0 files unresolved
1277
1289
1278 $ hg merge thatbranch
1290 $ hg merge thatbranch
1279 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1291 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1280 (branch merge, don't forget to commit)
1292 (branch merge, don't forget to commit)
1281
1293
1282 $ hg commit -i -m'will abort'
1294 $ hg commit -i -m'will abort'
1283 abort: cannot partially commit a merge (use "hg commit" instead)
1295 abort: cannot partially commit a merge (use "hg commit" instead)
1284 [255]
1296 [255]
1285
1297
1286 $ hg up -C
1298 $ hg up -C
1287 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1299 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1288
1300
1289 Editing patch (and ignoring trailing text)
1301 Editing patch (and ignoring trailing text)
1290
1302
1291 $ cat > editor.sh << '__EOF__'
1303 $ cat > editor.sh << '__EOF__'
1292 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1304 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1293 > trailing\nditto' "$1" > tmp
1305 > trailing\nditto' "$1" > tmp
1294 > mv tmp "$1"
1306 > mv tmp "$1"
1295 > __EOF__
1307 > __EOF__
1296 $ cat > editedfile << '__EOF__'
1308 $ cat > editedfile << '__EOF__'
1297 > This is the first line
1309 > This is the first line
1298 > This is the second line
1310 > This is the second line
1299 > This is the third line
1311 > This is the third line
1300 > __EOF__
1312 > __EOF__
1301 $ hg add editedfile
1313 $ hg add editedfile
1302 $ hg commit -medit-patch-1
1314 $ hg commit -medit-patch-1
1303 $ cat > editedfile << '__EOF__'
1315 $ cat > editedfile << '__EOF__'
1304 > This line has changed
1316 > This line has changed
1305 > This change will be committed
1317 > This change will be committed
1306 > This is the third line
1318 > This is the third line
1307 > __EOF__
1319 > __EOF__
1308 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1320 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1309 > y
1321 > y
1310 > e
1322 > e
1311 > EOF
1323 > EOF
1312 diff --git a/editedfile b/editedfile
1324 diff --git a/editedfile b/editedfile
1313 1 hunks, 2 lines changed
1325 1 hunks, 2 lines changed
1314 examine changes to 'editedfile'? [Ynesfdaq?] y
1326 examine changes to 'editedfile'? [Ynesfdaq?] y
1315
1327
1316 @@ -1,3 +1,3 @@
1328 @@ -1,3 +1,3 @@
1317 -This is the first line
1329 -This is the first line
1318 -This is the second line
1330 -This is the second line
1319 +This line has changed
1331 +This line has changed
1320 +This change will be committed
1332 +This change will be committed
1321 This is the third line
1333 This is the third line
1322 record this change to 'editedfile'? [Ynesfdaq?] e
1334 record this change to 'editedfile'? [Ynesfdaq?] e
1323
1335
1324 $ cat editedfile
1336 $ cat editedfile
1325 This line has changed
1337 This line has changed
1326 This change will be committed
1338 This change will be committed
1327 This is the third line
1339 This is the third line
1328 $ hg cat -r tip editedfile
1340 $ hg cat -r tip editedfile
1329 This is the first line
1341 This is the first line
1330 This change will be committed
1342 This change will be committed
1331 This is the third line
1343 This is the third line
1332 $ hg revert editedfile
1344 $ hg revert editedfile
1333
1345
1334 Trying to edit patch for whole file
1346 Trying to edit patch for whole file
1335
1347
1336 $ echo "This is the fourth line" >> editedfile
1348 $ echo "This is the fourth line" >> editedfile
1337 $ hg commit -i <<EOF
1349 $ hg commit -i <<EOF
1338 > e
1350 > e
1339 > q
1351 > q
1340 > EOF
1352 > EOF
1341 diff --git a/editedfile b/editedfile
1353 diff --git a/editedfile b/editedfile
1342 1 hunks, 1 lines changed
1354 1 hunks, 1 lines changed
1343 examine changes to 'editedfile'? [Ynesfdaq?] e
1355 examine changes to 'editedfile'? [Ynesfdaq?] e
1344
1356
1345 cannot edit patch for whole file
1357 cannot edit patch for whole file
1346 examine changes to 'editedfile'? [Ynesfdaq?] q
1358 examine changes to 'editedfile'? [Ynesfdaq?] q
1347
1359
1348 abort: user quit
1360 abort: user quit
1349 [255]
1361 [255]
1350 $ hg revert editedfile
1362 $ hg revert editedfile
1351
1363
1352 Removing changes from patch
1364 Removing changes from patch
1353
1365
1354 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1366 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1355 $ mv tmp editedfile
1367 $ mv tmp editedfile
1356 $ echo "This line has been added" >> editedfile
1368 $ echo "This line has been added" >> editedfile
1357 $ cat > editor.sh << '__EOF__'
1369 $ cat > editor.sh << '__EOF__'
1358 > sed -e 's/^[-+]/ /' "$1" > tmp
1370 > sed -e 's/^[-+]/ /' "$1" > tmp
1359 > mv tmp "$1"
1371 > mv tmp "$1"
1360 > __EOF__
1372 > __EOF__
1361 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1373 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1362 > y
1374 > y
1363 > e
1375 > e
1364 > EOF
1376 > EOF
1365 diff --git a/editedfile b/editedfile
1377 diff --git a/editedfile b/editedfile
1366 1 hunks, 3 lines changed
1378 1 hunks, 3 lines changed
1367 examine changes to 'editedfile'? [Ynesfdaq?] y
1379 examine changes to 'editedfile'? [Ynesfdaq?] y
1368
1380
1369 @@ -1,3 +1,3 @@
1381 @@ -1,3 +1,3 @@
1370 -This is the first line
1382 -This is the first line
1371 -This change will be committed
1383 -This change will be committed
1372 -This is the third line
1384 -This is the third line
1373 +This change will not be committed
1385 +This change will not be committed
1374 +This is the second line
1386 +This is the second line
1375 +This line has been added
1387 +This line has been added
1376 record this change to 'editedfile'? [Ynesfdaq?] e
1388 record this change to 'editedfile'? [Ynesfdaq?] e
1377
1389
1378 no changes to record
1390 no changes to record
1379 [1]
1391 [1]
1380 $ cat editedfile
1392 $ cat editedfile
1381 This change will not be committed
1393 This change will not be committed
1382 This is the second line
1394 This is the second line
1383 This line has been added
1395 This line has been added
1384 $ hg cat -r tip editedfile
1396 $ hg cat -r tip editedfile
1385 This is the first line
1397 This is the first line
1386 This change will be committed
1398 This change will be committed
1387 This is the third line
1399 This is the third line
1388 $ hg revert editedfile
1400 $ hg revert editedfile
1389
1401
1390 Invalid patch
1402 Invalid patch
1391
1403
1392 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1404 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1393 $ mv tmp editedfile
1405 $ mv tmp editedfile
1394 $ echo "This line has been added" >> editedfile
1406 $ echo "This line has been added" >> editedfile
1395 $ cat > editor.sh << '__EOF__'
1407 $ cat > editor.sh << '__EOF__'
1396 > sed s/This/That/ "$1" > tmp
1408 > sed s/This/That/ "$1" > tmp
1397 > mv tmp "$1"
1409 > mv tmp "$1"
1398 > __EOF__
1410 > __EOF__
1399 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1411 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1400 > y
1412 > y
1401 > e
1413 > e
1402 > EOF
1414 > EOF
1403 diff --git a/editedfile b/editedfile
1415 diff --git a/editedfile b/editedfile
1404 1 hunks, 3 lines changed
1416 1 hunks, 3 lines changed
1405 examine changes to 'editedfile'? [Ynesfdaq?] y
1417 examine changes to 'editedfile'? [Ynesfdaq?] y
1406
1418
1407 @@ -1,3 +1,3 @@
1419 @@ -1,3 +1,3 @@
1408 -This is the first line
1420 -This is the first line
1409 -This change will be committed
1421 -This change will be committed
1410 -This is the third line
1422 -This is the third line
1411 +This change will not be committed
1423 +This change will not be committed
1412 +This is the second line
1424 +This is the second line
1413 +This line has been added
1425 +This line has been added
1414 record this change to 'editedfile'? [Ynesfdaq?] e
1426 record this change to 'editedfile'? [Ynesfdaq?] e
1415
1427
1416 patching file editedfile
1428 patching file editedfile
1417 Hunk #1 FAILED at 0
1429 Hunk #1 FAILED at 0
1418 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1430 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1419 abort: patch failed to apply
1431 abort: patch failed to apply
1420 [255]
1432 [255]
1421 $ cat editedfile
1433 $ cat editedfile
1422 This change will not be committed
1434 This change will not be committed
1423 This is the second line
1435 This is the second line
1424 This line has been added
1436 This line has been added
1425 $ hg cat -r tip editedfile
1437 $ hg cat -r tip editedfile
1426 This is the first line
1438 This is the first line
1427 This change will be committed
1439 This change will be committed
1428 This is the third line
1440 This is the third line
1429 $ cat editedfile.rej
1441 $ cat editedfile.rej
1430 --- editedfile
1442 --- editedfile
1431 +++ editedfile
1443 +++ editedfile
1432 @@ -1,3 +1,3 @@
1444 @@ -1,3 +1,3 @@
1433 -That is the first line
1445 -That is the first line
1434 -That change will be committed
1446 -That change will be committed
1435 -That is the third line
1447 -That is the third line
1436 +That change will not be committed
1448 +That change will not be committed
1437 +That is the second line
1449 +That is the second line
1438 +That line has been added
1450 +That line has been added
1439
1451
1440 Malformed patch - error handling
1452 Malformed patch - error handling
1441
1453
1442 $ cat > editor.sh << '__EOF__'
1454 $ cat > editor.sh << '__EOF__'
1443 > sed -e '/^@/p' "$1" > tmp
1455 > sed -e '/^@/p' "$1" > tmp
1444 > mv tmp "$1"
1456 > mv tmp "$1"
1445 > __EOF__
1457 > __EOF__
1446 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1458 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1447 > y
1459 > y
1448 > e
1460 > e
1449 > EOF
1461 > EOF
1450 diff --git a/editedfile b/editedfile
1462 diff --git a/editedfile b/editedfile
1451 1 hunks, 3 lines changed
1463 1 hunks, 3 lines changed
1452 examine changes to 'editedfile'? [Ynesfdaq?] y
1464 examine changes to 'editedfile'? [Ynesfdaq?] y
1453
1465
1454 @@ -1,3 +1,3 @@
1466 @@ -1,3 +1,3 @@
1455 -This is the first line
1467 -This is the first line
1456 -This change will be committed
1468 -This change will be committed
1457 -This is the third line
1469 -This is the third line
1458 +This change will not be committed
1470 +This change will not be committed
1459 +This is the second line
1471 +This is the second line
1460 +This line has been added
1472 +This line has been added
1461 record this change to 'editedfile'? [Ynesfdaq?] e
1473 record this change to 'editedfile'? [Ynesfdaq?] e
1462
1474
1463 abort: error parsing patch: unhandled transition: range -> range
1475 abort: error parsing patch: unhandled transition: range -> range
1464 [255]
1476 [255]
1465
1477
1466 Exiting editor with status 1, ignores the edit but does not stop the recording
1478 Exiting editor with status 1, ignores the edit but does not stop the recording
1467 session
1479 session
1468
1480
1469 $ HGEDITOR=false hg commit -i <<EOF
1481 $ HGEDITOR=false hg commit -i <<EOF
1470 > y
1482 > y
1471 > e
1483 > e
1472 > n
1484 > n
1473 > EOF
1485 > EOF
1474 diff --git a/editedfile b/editedfile
1486 diff --git a/editedfile b/editedfile
1475 1 hunks, 3 lines changed
1487 1 hunks, 3 lines changed
1476 examine changes to 'editedfile'? [Ynesfdaq?] y
1488 examine changes to 'editedfile'? [Ynesfdaq?] y
1477
1489
1478 @@ -1,3 +1,3 @@
1490 @@ -1,3 +1,3 @@
1479 -This is the first line
1491 -This is the first line
1480 -This change will be committed
1492 -This change will be committed
1481 -This is the third line
1493 -This is the third line
1482 +This change will not be committed
1494 +This change will not be committed
1483 +This is the second line
1495 +This is the second line
1484 +This line has been added
1496 +This line has been added
1485 record this change to 'editedfile'? [Ynesfdaq?] e
1497 record this change to 'editedfile'? [Ynesfdaq?] e
1486
1498
1487 editor exited with exit code 1
1499 editor exited with exit code 1
1488 record this change to 'editedfile'? [Ynesfdaq?] n
1500 record this change to 'editedfile'? [Ynesfdaq?] n
1489
1501
1490 no changes to record
1502 no changes to record
1491 [1]
1503 [1]
1492
1504
1493
1505
1494 random text in random positions is still an error
1506 random text in random positions is still an error
1495
1507
1496 $ cat > editor.sh << '__EOF__'
1508 $ cat > editor.sh << '__EOF__'
1497 > sed -e '/^@/i\
1509 > sed -e '/^@/i\
1498 > other' "$1" > tmp
1510 > other' "$1" > tmp
1499 > mv tmp "$1"
1511 > mv tmp "$1"
1500 > __EOF__
1512 > __EOF__
1501 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1513 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1502 > y
1514 > y
1503 > e
1515 > e
1504 > EOF
1516 > EOF
1505 diff --git a/editedfile b/editedfile
1517 diff --git a/editedfile b/editedfile
1506 1 hunks, 3 lines changed
1518 1 hunks, 3 lines changed
1507 examine changes to 'editedfile'? [Ynesfdaq?] y
1519 examine changes to 'editedfile'? [Ynesfdaq?] y
1508
1520
1509 @@ -1,3 +1,3 @@
1521 @@ -1,3 +1,3 @@
1510 -This is the first line
1522 -This is the first line
1511 -This change will be committed
1523 -This change will be committed
1512 -This is the third line
1524 -This is the third line
1513 +This change will not be committed
1525 +This change will not be committed
1514 +This is the second line
1526 +This is the second line
1515 +This line has been added
1527 +This line has been added
1516 record this change to 'editedfile'? [Ynesfdaq?] e
1528 record this change to 'editedfile'? [Ynesfdaq?] e
1517
1529
1518 abort: error parsing patch: unhandled transition: file -> other
1530 abort: error parsing patch: unhandled transition: file -> other
1519 [255]
1531 [255]
1520
1532
1521 $ hg up -C
1533 $ hg up -C
1522 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1534 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1523
1535
1524 With win32text
1536 With win32text
1525
1537
1526 $ echo '[extensions]' >> .hg/hgrc
1538 $ echo '[extensions]' >> .hg/hgrc
1527 $ echo 'win32text = ' >> .hg/hgrc
1539 $ echo 'win32text = ' >> .hg/hgrc
1528 $ echo '[decode]' >> .hg/hgrc
1540 $ echo '[decode]' >> .hg/hgrc
1529 $ echo '** = cleverdecode:' >> .hg/hgrc
1541 $ echo '** = cleverdecode:' >> .hg/hgrc
1530 $ echo '[encode]' >> .hg/hgrc
1542 $ echo '[encode]' >> .hg/hgrc
1531 $ echo '** = cleverencode:' >> .hg/hgrc
1543 $ echo '** = cleverencode:' >> .hg/hgrc
1532 $ echo '[patch]' >> .hg/hgrc
1544 $ echo '[patch]' >> .hg/hgrc
1533 $ echo 'eol = crlf' >> .hg/hgrc
1545 $ echo 'eol = crlf' >> .hg/hgrc
1534
1546
1535 Ignore win32text deprecation warning for now:
1547 Ignore win32text deprecation warning for now:
1536
1548
1537 $ echo '[win32text]' >> .hg/hgrc
1549 $ echo '[win32text]' >> .hg/hgrc
1538 $ echo 'warn = no' >> .hg/hgrc
1550 $ echo 'warn = no' >> .hg/hgrc
1539
1551
1540 $ echo d >> subdir/f1
1552 $ echo d >> subdir/f1
1541 $ hg commit -i -d '24 0' -mw1 <<EOF
1553 $ hg commit -i -d '24 0' -mw1 <<EOF
1542 > y
1554 > y
1543 > y
1555 > y
1544 > EOF
1556 > EOF
1545 diff --git a/subdir/f1 b/subdir/f1
1557 diff --git a/subdir/f1 b/subdir/f1
1546 1 hunks, 1 lines changed
1558 1 hunks, 1 lines changed
1547 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1559 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1548
1560
1549 @@ -3,3 +3,4 @@ a
1561 @@ -3,3 +3,4 @@ a
1550 a
1562 a
1551 b
1563 b
1552 c
1564 c
1553 +d
1565 +d
1554 record this change to 'subdir/f1'? [Ynesfdaq?] y
1566 record this change to 'subdir/f1'? [Ynesfdaq?] y
1555
1567
1556
1568
1557 $ hg status -A subdir/f1
1569 $ hg status -A subdir/f1
1558 C subdir/f1
1570 C subdir/f1
1559 $ hg tip -p
1571 $ hg tip -p
1560 changeset: 30:* (glob)
1572 changeset: 30:* (glob)
1561 tag: tip
1573 tag: tip
1562 user: test
1574 user: test
1563 date: Thu Jan 01 00:00:24 1970 +0000
1575 date: Thu Jan 01 00:00:24 1970 +0000
1564 summary: w1
1576 summary: w1
1565
1577
1566 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1578 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1567 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1579 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1568 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1580 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1569 @@ -3,3 +3,4 @@
1581 @@ -3,3 +3,4 @@
1570 a
1582 a
1571 b
1583 b
1572 c
1584 c
1573 +d
1585 +d
1574
1586
1575
1587
1576
1588
1577 Test --user when ui.username not set
1589 Test --user when ui.username not set
1578 $ unset HGUSER
1590 $ unset HGUSER
1579 $ echo e >> subdir/f1
1591 $ echo e >> subdir/f1
1580 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1592 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1581 > y
1593 > y
1582 > y
1594 > y
1583 > EOF
1595 > EOF
1584 diff --git a/subdir/f1 b/subdir/f1
1596 diff --git a/subdir/f1 b/subdir/f1
1585 1 hunks, 1 lines changed
1597 1 hunks, 1 lines changed
1586 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1598 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1587
1599
1588 @@ -4,3 +4,4 @@ a
1600 @@ -4,3 +4,4 @@ a
1589 b
1601 b
1590 c
1602 c
1591 d
1603 d
1592 +e
1604 +e
1593 record this change to 'subdir/f1'? [Ynesfdaq?] y
1605 record this change to 'subdir/f1'? [Ynesfdaq?] y
1594
1606
1595 $ hg status -A subdir/f1
1607 $ hg status -A subdir/f1
1596 C subdir/f1
1608 C subdir/f1
1597 $ hg log --template '{author}\n' -l 1
1609 $ hg log --template '{author}\n' -l 1
1598 xyz
1610 xyz
1599 $ HGUSER="test"
1611 $ HGUSER="test"
1600 $ export HGUSER
1612 $ export HGUSER
1601
1613
1602
1614
1603 Moving files
1615 Moving files
1604
1616
1605 $ hg update -C .
1617 $ hg update -C .
1606 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1618 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1607 $ hg mv plain plain3
1619 $ hg mv plain plain3
1608 $ echo somechange >> plain3
1620 $ echo somechange >> plain3
1609 $ hg commit -i -d '23 0' -mmoving_files << EOF
1621 $ hg commit -i -d '23 0' -mmoving_files << EOF
1610 > y
1622 > y
1611 > y
1623 > y
1612 > EOF
1624 > EOF
1613 diff --git a/plain b/plain3
1625 diff --git a/plain b/plain3
1614 rename from plain
1626 rename from plain
1615 rename to plain3
1627 rename to plain3
1616 1 hunks, 1 lines changed
1628 1 hunks, 1 lines changed
1617 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1629 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1618
1630
1619 @@ -11,3 +11,4 @@ 8
1631 @@ -11,3 +11,4 @@ 8
1620 9
1632 9
1621 10
1633 10
1622 11
1634 11
1623 +somechange
1635 +somechange
1624 record this change to 'plain3'? [Ynesfdaq?] y
1636 record this change to 'plain3'? [Ynesfdaq?] y
1625
1637
1626 The #if execbit block above changes the hash here on some systems
1638 The #if execbit block above changes the hash here on some systems
1627 $ hg status -A plain3
1639 $ hg status -A plain3
1628 C plain3
1640 C plain3
1629 $ hg tip
1641 $ hg tip
1630 changeset: 32:* (glob)
1642 changeset: 32:* (glob)
1631 tag: tip
1643 tag: tip
1632 user: test
1644 user: test
1633 date: Thu Jan 01 00:00:23 1970 +0000
1645 date: Thu Jan 01 00:00:23 1970 +0000
1634 summary: moving_files
1646 summary: moving_files
1635
1647
1636 Editing patch of newly added file
1648 Editing patch of newly added file
1637
1649
1638 $ hg update -C .
1650 $ hg update -C .
1639 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1651 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1640 $ cat > editor.sh << '__EOF__'
1652 $ cat > editor.sh << '__EOF__'
1641 > cat "$1" | sed "s/first/very/g" > tt
1653 > cat "$1" | sed "s/first/very/g" > tt
1642 > mv tt "$1"
1654 > mv tt "$1"
1643 > __EOF__
1655 > __EOF__
1644 $ cat > newfile << '__EOF__'
1656 $ cat > newfile << '__EOF__'
1645 > This is the first line
1657 > This is the first line
1646 > This is the second line
1658 > This is the second line
1647 > This is the third line
1659 > This is the third line
1648 > __EOF__
1660 > __EOF__
1649 $ hg add newfile
1661 $ hg add newfile
1650 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1662 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1651 > y
1663 > y
1652 > e
1664 > e
1653 > EOF
1665 > EOF
1654 diff --git a/newfile b/newfile
1666 diff --git a/newfile b/newfile
1655 new file mode 100644
1667 new file mode 100644
1656 examine changes to 'newfile'? [Ynesfdaq?] y
1668 examine changes to 'newfile'? [Ynesfdaq?] y
1657
1669
1658 @@ -0,0 +1,3 @@
1670 @@ -0,0 +1,3 @@
1659 +This is the first line
1671 +This is the first line
1660 +This is the second line
1672 +This is the second line
1661 +This is the third line
1673 +This is the third line
1662 record this change to 'newfile'? [Ynesfdaq?] e
1674 record this change to 'newfile'? [Ynesfdaq?] e
1663
1675
1664 $ hg cat -r tip newfile
1676 $ hg cat -r tip newfile
1665 This is the very line
1677 This is the very line
1666 This is the second line
1678 This is the second line
1667 This is the third line
1679 This is the third line
1668
1680
1669 $ cat newfile
1681 $ cat newfile
1670 This is the first line
1682 This is the first line
1671 This is the second line
1683 This is the second line
1672 This is the third line
1684 This is the third line
1673
1685
1674 Add new file from within a subdirectory
1686 Add new file from within a subdirectory
1675 $ hg update -C .
1687 $ hg update -C .
1676 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1688 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1677 $ mkdir folder
1689 $ mkdir folder
1678 $ cd folder
1690 $ cd folder
1679 $ echo "foo" > bar
1691 $ echo "foo" > bar
1680 $ hg add bar
1692 $ hg add bar
1681 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1693 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1682 > y
1694 > y
1683 > y
1695 > y
1684 > EOF
1696 > EOF
1685 diff --git a/folder/bar b/folder/bar
1697 diff --git a/folder/bar b/folder/bar
1686 new file mode 100644
1698 new file mode 100644
1687 examine changes to 'folder/bar'? [Ynesfdaq?] y
1699 examine changes to 'folder/bar'? [Ynesfdaq?] y
1688
1700
1689 @@ -0,0 +1,1 @@
1701 @@ -0,0 +1,1 @@
1690 +foo
1702 +foo
1691 record this change to 'folder/bar'? [Ynesfdaq?] y
1703 record this change to 'folder/bar'? [Ynesfdaq?] y
1692
1704
1693 The #if execbit block above changes the hashes here on some systems
1705 The #if execbit block above changes the hashes here on some systems
1694 $ hg tip -p
1706 $ hg tip -p
1695 changeset: 34:* (glob)
1707 changeset: 34:* (glob)
1696 tag: tip
1708 tag: tip
1697 user: test
1709 user: test
1698 date: Thu Jan 01 00:00:23 1970 +0000
1710 date: Thu Jan 01 00:00:23 1970 +0000
1699 summary: newfilesubdir
1711 summary: newfilesubdir
1700
1712
1701 diff -r * -r * folder/bar (glob)
1713 diff -r * -r * folder/bar (glob)
1702 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1714 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1703 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1715 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1704 @@ -0,0 +1,1 @@
1716 @@ -0,0 +1,1 @@
1705 +foo
1717 +foo
1706
1718
1707 $ cd ..
1719 $ cd ..
1708
1720
1709 $ hg status -A folder/bar
1721 $ hg status -A folder/bar
1710 C folder/bar
1722 C folder/bar
1711
1723
1712 Clear win32text configuration before size/timestamp sensitive test
1724 Clear win32text configuration before size/timestamp sensitive test
1713
1725
1714 $ cat >> .hg/hgrc <<EOF
1726 $ cat >> .hg/hgrc <<EOF
1715 > [extensions]
1727 > [extensions]
1716 > win32text = !
1728 > win32text = !
1717 > [decode]
1729 > [decode]
1718 > ** = !
1730 > ** = !
1719 > [encode]
1731 > [encode]
1720 > ** = !
1732 > ** = !
1721 > [patch]
1733 > [patch]
1722 > eol = strict
1734 > eol = strict
1723 > EOF
1735 > EOF
1724 $ hg update -q -C null
1736 $ hg update -q -C null
1725 $ hg update -q -C tip
1737 $ hg update -q -C tip
1726
1738
1727 Test that partially committed file is still treated as "modified",
1739 Test that partially committed file is still treated as "modified",
1728 even if none of mode, size and timestamp is changed on the filesystem
1740 even if none of mode, size and timestamp is changed on the filesystem
1729 (see also issue4583).
1741 (see also issue4583).
1730
1742
1731 $ cat > subdir/f1 <<EOF
1743 $ cat > subdir/f1 <<EOF
1732 > A
1744 > A
1733 > a
1745 > a
1734 > a
1746 > a
1735 > b
1747 > b
1736 > c
1748 > c
1737 > d
1749 > d
1738 > E
1750 > E
1739 > EOF
1751 > EOF
1740 $ hg diff --git subdir/f1
1752 $ hg diff --git subdir/f1
1741 diff --git a/subdir/f1 b/subdir/f1
1753 diff --git a/subdir/f1 b/subdir/f1
1742 --- a/subdir/f1
1754 --- a/subdir/f1
1743 +++ b/subdir/f1
1755 +++ b/subdir/f1
1744 @@ -1,7 +1,7 @@
1756 @@ -1,7 +1,7 @@
1745 -a
1757 -a
1746 +A
1758 +A
1747 a
1759 a
1748 a
1760 a
1749 b
1761 b
1750 c
1762 c
1751 d
1763 d
1752 -e
1764 -e
1753 +E
1765 +E
1754
1766
1755 $ touch -t 200001010000 subdir/f1
1767 $ touch -t 200001010000 subdir/f1
1756
1768
1757 $ cat >> .hg/hgrc <<EOF
1769 $ cat >> .hg/hgrc <<EOF
1758 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
1770 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
1759 > [fakepatchtime]
1771 > [fakepatchtime]
1760 > fakenow = 200001010000
1772 > fakenow = 200001010000
1761 >
1773 >
1762 > [extensions]
1774 > [extensions]
1763 > fakepatchtime = $TESTDIR/fakepatchtime.py
1775 > fakepatchtime = $TESTDIR/fakepatchtime.py
1764 > EOF
1776 > EOF
1765 $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
1777 $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
1766 > y
1778 > y
1767 > y
1779 > y
1768 > n
1780 > n
1769 > EOF
1781 > EOF
1770 diff --git a/subdir/f1 b/subdir/f1
1782 diff --git a/subdir/f1 b/subdir/f1
1771 2 hunks, 2 lines changed
1783 2 hunks, 2 lines changed
1772 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1784 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1773
1785
1774 @@ -1,6 +1,6 @@
1786 @@ -1,6 +1,6 @@
1775 -a
1787 -a
1776 +A
1788 +A
1777 a
1789 a
1778 a
1790 a
1779 b
1791 b
1780 c
1792 c
1781 d
1793 d
1782 record change 1/2 to 'subdir/f1'? [Ynesfdaq?] y
1794 record change 1/2 to 'subdir/f1'? [Ynesfdaq?] y
1783
1795
1784 @@ -2,6 +2,6 @@
1796 @@ -2,6 +2,6 @@
1785 a
1797 a
1786 a
1798 a
1787 b
1799 b
1788 c
1800 c
1789 d
1801 d
1790 -e
1802 -e
1791 +E
1803 +E
1792 record change 2/2 to 'subdir/f1'? [Ynesfdaq?] n
1804 record change 2/2 to 'subdir/f1'? [Ynesfdaq?] n
1793
1805
1794 $ cat >> .hg/hgrc <<EOF
1806 $ cat >> .hg/hgrc <<EOF
1795 > [extensions]
1807 > [extensions]
1796 > fakepatchtime = !
1808 > fakepatchtime = !
1797 > EOF
1809 > EOF
1798
1810
1799 $ hg debugstate | grep ' subdir/f1$'
1811 $ hg debugstate | grep ' subdir/f1$'
1800 n 0 -1 unset subdir/f1
1812 n 0 -1 unset subdir/f1
1801 $ hg status -A subdir/f1
1813 $ hg status -A subdir/f1
1802 M subdir/f1
1814 M subdir/f1
1803
1815
1804 Test commands.commit.interactive.unified=0
1816 Test commands.commit.interactive.unified=0
1805
1817
1806 $ hg init $TESTTMP/b
1818 $ hg init $TESTTMP/b
1807 $ cd $TESTTMP/b
1819 $ cd $TESTTMP/b
1808 $ cat > foo <<EOF
1820 $ cat > foo <<EOF
1809 > 1
1821 > 1
1810 > 2
1822 > 2
1811 > 3
1823 > 3
1812 > 4
1824 > 4
1813 > 5
1825 > 5
1814 > EOF
1826 > EOF
1815 $ hg ci -qAm initial
1827 $ hg ci -qAm initial
1816 $ cat > foo <<EOF
1828 $ cat > foo <<EOF
1817 > 1
1829 > 1
1818 > change1
1830 > change1
1819 > 2
1831 > 2
1820 > 3
1832 > 3
1821 > change2
1833 > change2
1822 > 4
1834 > 4
1823 > 5
1835 > 5
1824 > EOF
1836 > EOF
1825 $ printf 'y\ny\ny\n' | hg ci -im initial --config commands.commit.interactive.unified=0
1837 $ printf 'y\ny\ny\n' | hg ci -im initial --config commands.commit.interactive.unified=0
1826 diff --git a/foo b/foo
1838 diff --git a/foo b/foo
1827 2 hunks, 2 lines changed
1839 2 hunks, 2 lines changed
1828 examine changes to 'foo'? [Ynesfdaq?] y
1840 examine changes to 'foo'? [Ynesfdaq?] y
1829
1841
1830 @@ -1,0 +2,1 @@ 1
1842 @@ -1,0 +2,1 @@ 1
1831 +change1
1843 +change1
1832 record change 1/2 to 'foo'? [Ynesfdaq?] y
1844 record change 1/2 to 'foo'? [Ynesfdaq?] y
1833
1845
1834 @@ -3,0 +5,1 @@ 3
1846 @@ -3,0 +5,1 @@ 3
1835 +change2
1847 +change2
1836 record change 2/2 to 'foo'? [Ynesfdaq?] y
1848 record change 2/2 to 'foo'? [Ynesfdaq?] y
1837
1849
1838 $ cd $TESTTMP
1850 $ cd $TESTTMP
1839
1851
1840 Test diff.ignoreblanklines=1
1852 Test diff.ignoreblanklines=1
1841
1853
1842 $ hg init c
1854 $ hg init c
1843 $ cd c
1855 $ cd c
1844 $ cat > foo <<EOF
1856 $ cat > foo <<EOF
1845 > 1
1857 > 1
1846 > 2
1858 > 2
1847 > 3
1859 > 3
1848 > 4
1860 > 4
1849 > 5
1861 > 5
1850 > EOF
1862 > EOF
1851 $ hg ci -qAm initial
1863 $ hg ci -qAm initial
1852 $ cat > foo <<EOF
1864 $ cat > foo <<EOF
1853 > 1
1865 > 1
1854 >
1866 >
1855 > 2
1867 > 2
1856 > 3
1868 > 3
1857 > change2
1869 > change2
1858 > 4
1870 > 4
1859 > 5
1871 > 5
1860 > EOF
1872 > EOF
1861 $ printf 'y\ny\ny\n' | hg ci -im initial --config diff.ignoreblanklines=1
1873 $ printf 'y\ny\ny\n' | hg ci -im initial --config diff.ignoreblanklines=1
1862 diff --git a/foo b/foo
1874 diff --git a/foo b/foo
1863 2 hunks, 2 lines changed
1875 2 hunks, 2 lines changed
1864 examine changes to 'foo'? [Ynesfdaq?] y
1876 examine changes to 'foo'? [Ynesfdaq?] y
1865
1877
1866 @@ -1,3 +1,4 @@
1878 @@ -1,3 +1,4 @@
1867 1
1879 1
1868 +
1880 +
1869 2
1881 2
1870 3
1882 3
1871 record change 1/2 to 'foo'? [Ynesfdaq?] y
1883 record change 1/2 to 'foo'? [Ynesfdaq?] y
1872
1884
1873 @@ -2,4 +3,5 @@
1885 @@ -2,4 +3,5 @@
1874 2
1886 2
1875 3
1887 3
1876 +change2
1888 +change2
1877 4
1889 4
1878 5
1890 5
1879 record change 2/2 to 'foo'? [Ynesfdaq?] y
1891 record change 2/2 to 'foo'? [Ynesfdaq?] y
1880
1892
1881
1893
@@ -1,3041 +1,3047 b''
1 $ HGENCODING=utf-8
1 $ HGENCODING=utf-8
2 $ export HGENCODING
2 $ export HGENCODING
3 $ cat > testrevset.py << EOF
3 $ cat > testrevset.py << EOF
4 > import mercurial.revset
4 > import mercurial.revset
5 >
5 >
6 > baseset = mercurial.revset.baseset
6 > baseset = mercurial.revset.baseset
7 >
7 >
8 > def r3232(repo, subset, x):
8 > def r3232(repo, subset, x):
9 > """"simple revset that return [3,2,3,2]
9 > """"simple revset that return [3,2,3,2]
10 >
10 >
11 > revisions duplicated on purpose.
11 > revisions duplicated on purpose.
12 > """
12 > """
13 > if 3 not in subset:
13 > if 3 not in subset:
14 > if 2 in subset:
14 > if 2 in subset:
15 > return baseset([2, 2])
15 > return baseset([2, 2])
16 > return baseset()
16 > return baseset()
17 > return baseset([3, 3, 2, 2])
17 > return baseset([3, 3, 2, 2])
18 >
18 >
19 > mercurial.revset.symbols[b'r3232'] = r3232
19 > mercurial.revset.symbols[b'r3232'] = r3232
20 > EOF
20 > EOF
21 $ cat >> $HGRCPATH << EOF
21 $ cat >> $HGRCPATH << EOF
22 > [extensions]
22 > [extensions]
23 > drawdag=$TESTDIR/drawdag.py
23 > drawdag=$TESTDIR/drawdag.py
24 > testrevset=$TESTTMP/testrevset.py
24 > testrevset=$TESTTMP/testrevset.py
25 > EOF
25 > EOF
26
26
27 $ try() {
27 $ try() {
28 > hg debugrevspec --debug "$@"
28 > hg debugrevspec --debug "$@"
29 > }
29 > }
30
30
31 $ log() {
31 $ log() {
32 > hg log --template '{rev}\n' -r "$1"
32 > hg log --template '{rev}\n' -r "$1"
33 > }
33 > }
34
34
35 extension to build '_intlist()' and '_hexlist()', which is necessary because
35 extension to build '_intlist()' and '_hexlist()', which is necessary because
36 these predicates use '\0' as a separator:
36 these predicates use '\0' as a separator:
37
37
38 $ cat <<EOF > debugrevlistspec.py
38 $ cat <<EOF > debugrevlistspec.py
39 > from __future__ import absolute_import
39 > from __future__ import absolute_import
40 > from mercurial import (
40 > from mercurial import (
41 > node as nodemod,
41 > node as nodemod,
42 > registrar,
42 > registrar,
43 > revset,
43 > revset,
44 > revsetlang,
44 > revsetlang,
45 > )
45 > )
46 > from mercurial.utils import stringutil
46 > from mercurial.utils import stringutil
47 > cmdtable = {}
47 > cmdtable = {}
48 > command = registrar.command(cmdtable)
48 > command = registrar.command(cmdtable)
49 > @command(b'debugrevlistspec',
49 > @command(b'debugrevlistspec',
50 > [(b'', b'optimize', None, b'print parsed tree after optimizing'),
50 > [(b'', b'optimize', None, b'print parsed tree after optimizing'),
51 > (b'', b'bin', None, b'unhexlify arguments')])
51 > (b'', b'bin', None, b'unhexlify arguments')])
52 > def debugrevlistspec(ui, repo, fmt, *args, **opts):
52 > def debugrevlistspec(ui, repo, fmt, *args, **opts):
53 > if opts['bin']:
53 > if opts['bin']:
54 > args = map(nodemod.bin, args)
54 > args = map(nodemod.bin, args)
55 > expr = revsetlang.formatspec(fmt, list(args))
55 > expr = revsetlang.formatspec(fmt, list(args))
56 > if ui.verbose:
56 > if ui.verbose:
57 > tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
57 > tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
58 > ui.note(revsetlang.prettyformat(tree), b"\n")
58 > ui.note(revsetlang.prettyformat(tree), b"\n")
59 > if opts["optimize"]:
59 > if opts["optimize"]:
60 > opttree = revsetlang.optimize(revsetlang.analyze(tree))
60 > opttree = revsetlang.optimize(revsetlang.analyze(tree))
61 > ui.note(b"* optimized:\n", revsetlang.prettyformat(opttree),
61 > ui.note(b"* optimized:\n", revsetlang.prettyformat(opttree),
62 > b"\n")
62 > b"\n")
63 > func = revset.match(ui, expr, lookup=revset.lookupfn(repo))
63 > func = revset.match(ui, expr, lookup=revset.lookupfn(repo))
64 > revs = func(repo)
64 > revs = func(repo)
65 > if ui.verbose:
65 > if ui.verbose:
66 > ui.note(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
66 > ui.note(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
67 > for c in revs:
67 > for c in revs:
68 > ui.write(b"%d\n" % c)
68 > ui.write(b"%d\n" % c)
69 > EOF
69 > EOF
70 $ cat <<EOF >> $HGRCPATH
70 $ cat <<EOF >> $HGRCPATH
71 > [extensions]
71 > [extensions]
72 > debugrevlistspec = $TESTTMP/debugrevlistspec.py
72 > debugrevlistspec = $TESTTMP/debugrevlistspec.py
73 > EOF
73 > EOF
74 $ trylist() {
74 $ trylist() {
75 > hg debugrevlistspec --debug "$@"
75 > hg debugrevlistspec --debug "$@"
76 > }
76 > }
77
77
78 $ hg init repo
78 $ hg init repo
79 $ cd repo
79 $ cd repo
80
80
81 $ echo a > a
81 $ echo a > a
82 $ hg branch a
82 $ hg branch a
83 marked working directory as branch a
83 marked working directory as branch a
84 (branches are permanent and global, did you want a bookmark?)
84 (branches are permanent and global, did you want a bookmark?)
85 $ hg ci -Aqm0
85 $ hg ci -Aqm0
86
86
87 $ echo b > b
87 $ echo b > b
88 $ hg branch b
88 $ hg branch b
89 marked working directory as branch b
89 marked working directory as branch b
90 $ hg ci -Aqm1
90 $ hg ci -Aqm1
91
91
92 $ rm a
92 $ rm a
93 $ hg branch a-b-c-
93 $ hg branch a-b-c-
94 marked working directory as branch a-b-c-
94 marked working directory as branch a-b-c-
95 $ hg ci -Aqm2 -u Bob
95 $ hg ci -Aqm2 -u Bob
96
96
97 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
97 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
98 2
98 2
99 $ hg log -r "extra('branch')" --template '{rev}\n'
99 $ hg log -r "extra('branch')" --template '{rev}\n'
100 0
100 0
101 1
101 1
102 2
102 2
103 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
103 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
104 0 a
104 0 a
105 2 a-b-c-
105 2 a-b-c-
106
106
107 $ hg co 1
107 $ hg co 1
108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 $ hg branch +a+b+c+
109 $ hg branch +a+b+c+
110 marked working directory as branch +a+b+c+
110 marked working directory as branch +a+b+c+
111 $ hg ci -Aqm3
111 $ hg ci -Aqm3
112
112
113 $ hg co 2 # interleave
113 $ hg co 2 # interleave
114 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
114 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
115 $ echo bb > b
115 $ echo bb > b
116 $ hg branch -- -a-b-c-
116 $ hg branch -- -a-b-c-
117 marked working directory as branch -a-b-c-
117 marked working directory as branch -a-b-c-
118 $ hg ci -Aqm4 -d "May 12 2005"
118 $ hg ci -Aqm4 -d "May 12 2005"
119
119
120 $ hg co 3
120 $ hg co 3
121 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 $ hg branch !a/b/c/
122 $ hg branch !a/b/c/
123 marked working directory as branch !a/b/c/
123 marked working directory as branch !a/b/c/
124 $ hg ci -Aqm"5 bug"
124 $ hg ci -Aqm"5 bug"
125
125
126 $ hg merge 4
126 $ hg merge 4
127 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
127 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
128 (branch merge, don't forget to commit)
128 (branch merge, don't forget to commit)
129 $ hg branch _a_b_c_
129 $ hg branch _a_b_c_
130 marked working directory as branch _a_b_c_
130 marked working directory as branch _a_b_c_
131 $ hg ci -Aqm"6 issue619"
131 $ hg ci -Aqm"6 issue619"
132
132
133 $ hg branch .a.b.c.
133 $ hg branch .a.b.c.
134 marked working directory as branch .a.b.c.
134 marked working directory as branch .a.b.c.
135 $ hg ci -Aqm7
135 $ hg ci -Aqm7
136
136
137 $ hg branch all
137 $ hg branch all
138 marked working directory as branch all
138 marked working directory as branch all
139
139
140 $ hg co 4
140 $ hg co 4
141 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 $ hg branch Γ©
142 $ hg branch Γ©
143 marked working directory as branch \xc3\xa9 (esc)
143 marked working directory as branch \xc3\xa9 (esc)
144 $ hg ci -Aqm9
144 $ hg ci -Aqm9
145
145
146 $ hg tag -r6 1.0
146 $ hg tag -r6 1.0
147 $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
147 $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
148
148
149 $ hg clone --quiet -U -r 7 . ../remote1
149 $ hg clone --quiet -U -r 7 . ../remote1
150 $ hg clone --quiet -U -r 8 . ../remote2
150 $ hg clone --quiet -U -r 8 . ../remote2
151 $ echo "[paths]" >> .hg/hgrc
151 $ echo "[paths]" >> .hg/hgrc
152 $ echo "default = ../remote1" >> .hg/hgrc
152 $ echo "default = ../remote1" >> .hg/hgrc
153
153
154 trivial
154 trivial
155
155
156 $ try 0:1
156 $ try 0:1
157 (range
157 (range
158 (symbol '0')
158 (symbol '0')
159 (symbol '1'))
159 (symbol '1'))
160 * set:
160 * set:
161 <spanset+ 0:2>
161 <spanset+ 0:2>
162 0
162 0
163 1
163 1
164 $ try --optimize :
164 $ try --optimize :
165 (rangeall
165 (rangeall
166 None)
166 None)
167 * optimized:
167 * optimized:
168 (rangeall
168 (rangeall
169 None)
169 None)
170 * set:
170 * set:
171 <spanset+ 0:10>
171 <spanset+ 0:10>
172 0
172 0
173 1
173 1
174 2
174 2
175 3
175 3
176 4
176 4
177 5
177 5
178 6
178 6
179 7
179 7
180 8
180 8
181 9
181 9
182 $ try 3::6
182 $ try 3::6
183 (dagrange
183 (dagrange
184 (symbol '3')
184 (symbol '3')
185 (symbol '6'))
185 (symbol '6'))
186 * set:
186 * set:
187 <baseset+ [3, 5, 6]>
187 <baseset+ [3, 5, 6]>
188 3
188 3
189 5
189 5
190 6
190 6
191 $ try '0|1|2'
191 $ try '0|1|2'
192 (or
192 (or
193 (list
193 (list
194 (symbol '0')
194 (symbol '0')
195 (symbol '1')
195 (symbol '1')
196 (symbol '2')))
196 (symbol '2')))
197 * set:
197 * set:
198 <baseset [0, 1, 2]>
198 <baseset [0, 1, 2]>
199 0
199 0
200 1
200 1
201 2
201 2
202
202
203 names that should work without quoting
203 names that should work without quoting
204
204
205 $ try a
205 $ try a
206 (symbol 'a')
206 (symbol 'a')
207 * set:
207 * set:
208 <baseset [0]>
208 <baseset [0]>
209 0
209 0
210 $ try b-a
210 $ try b-a
211 (minus
211 (minus
212 (symbol 'b')
212 (symbol 'b')
213 (symbol 'a'))
213 (symbol 'a'))
214 * set:
214 * set:
215 <filteredset
215 <filteredset
216 <baseset [1]>,
216 <baseset [1]>,
217 <not
217 <not
218 <baseset [0]>>>
218 <baseset [0]>>>
219 1
219 1
220 $ try _a_b_c_
220 $ try _a_b_c_
221 (symbol '_a_b_c_')
221 (symbol '_a_b_c_')
222 * set:
222 * set:
223 <baseset [6]>
223 <baseset [6]>
224 6
224 6
225 $ try _a_b_c_-a
225 $ try _a_b_c_-a
226 (minus
226 (minus
227 (symbol '_a_b_c_')
227 (symbol '_a_b_c_')
228 (symbol 'a'))
228 (symbol 'a'))
229 * set:
229 * set:
230 <filteredset
230 <filteredset
231 <baseset [6]>,
231 <baseset [6]>,
232 <not
232 <not
233 <baseset [0]>>>
233 <baseset [0]>>>
234 6
234 6
235 $ try .a.b.c.
235 $ try .a.b.c.
236 (symbol '.a.b.c.')
236 (symbol '.a.b.c.')
237 * set:
237 * set:
238 <baseset [7]>
238 <baseset [7]>
239 7
239 7
240 $ try .a.b.c.-a
240 $ try .a.b.c.-a
241 (minus
241 (minus
242 (symbol '.a.b.c.')
242 (symbol '.a.b.c.')
243 (symbol 'a'))
243 (symbol 'a'))
244 * set:
244 * set:
245 <filteredset
245 <filteredset
246 <baseset [7]>,
246 <baseset [7]>,
247 <not
247 <not
248 <baseset [0]>>>
248 <baseset [0]>>>
249 7
249 7
250
250
251 names that should be caught by fallback mechanism
251 names that should be caught by fallback mechanism
252
252
253 $ try -- '-a-b-c-'
253 $ try -- '-a-b-c-'
254 (symbol '-a-b-c-')
254 (symbol '-a-b-c-')
255 * set:
255 * set:
256 <baseset [4]>
256 <baseset [4]>
257 4
257 4
258 $ log -a-b-c-
258 $ log -a-b-c-
259 4
259 4
260 $ try '+a+b+c+'
260 $ try '+a+b+c+'
261 (symbol '+a+b+c+')
261 (symbol '+a+b+c+')
262 * set:
262 * set:
263 <baseset [3]>
263 <baseset [3]>
264 3
264 3
265 $ try '+a+b+c+:'
265 $ try '+a+b+c+:'
266 (rangepost
266 (rangepost
267 (symbol '+a+b+c+'))
267 (symbol '+a+b+c+'))
268 * set:
268 * set:
269 <spanset+ 3:10>
269 <spanset+ 3:10>
270 3
270 3
271 4
271 4
272 5
272 5
273 6
273 6
274 7
274 7
275 8
275 8
276 9
276 9
277 $ try ':+a+b+c+'
277 $ try ':+a+b+c+'
278 (rangepre
278 (rangepre
279 (symbol '+a+b+c+'))
279 (symbol '+a+b+c+'))
280 * set:
280 * set:
281 <spanset+ 0:4>
281 <spanset+ 0:4>
282 0
282 0
283 1
283 1
284 2
284 2
285 3
285 3
286 $ try -- '-a-b-c-:+a+b+c+'
286 $ try -- '-a-b-c-:+a+b+c+'
287 (range
287 (range
288 (symbol '-a-b-c-')
288 (symbol '-a-b-c-')
289 (symbol '+a+b+c+'))
289 (symbol '+a+b+c+'))
290 * set:
290 * set:
291 <spanset- 3:5>
291 <spanset- 3:5>
292 4
292 4
293 3
293 3
294 $ log '-a-b-c-:+a+b+c+'
294 $ log '-a-b-c-:+a+b+c+'
295 4
295 4
296 3
296 3
297
297
298 $ try -- -a-b-c--a # complains
298 $ try -- -a-b-c--a # complains
299 (minus
299 (minus
300 (minus
300 (minus
301 (minus
301 (minus
302 (negate
302 (negate
303 (symbol 'a'))
303 (symbol 'a'))
304 (symbol 'b'))
304 (symbol 'b'))
305 (symbol 'c'))
305 (symbol 'c'))
306 (negate
306 (negate
307 (symbol 'a')))
307 (symbol 'a')))
308 abort: unknown revision '-a'!
308 abort: unknown revision '-a'!
309 [255]
309 [255]
310 $ try Γ©
310 $ try Γ©
311 (symbol '\xc3\xa9')
311 (symbol '\xc3\xa9')
312 * set:
312 * set:
313 <baseset [9]>
313 <baseset [9]>
314 9
314 9
315
315
316 no quoting needed
316 no quoting needed
317
317
318 $ log ::a-b-c-
318 $ log ::a-b-c-
319 0
319 0
320 1
320 1
321 2
321 2
322
322
323 quoting needed
323 quoting needed
324
324
325 $ try '"-a-b-c-"-a'
325 $ try '"-a-b-c-"-a'
326 (minus
326 (minus
327 (string '-a-b-c-')
327 (string '-a-b-c-')
328 (symbol 'a'))
328 (symbol 'a'))
329 * set:
329 * set:
330 <filteredset
330 <filteredset
331 <baseset [4]>,
331 <baseset [4]>,
332 <not
332 <not
333 <baseset [0]>>>
333 <baseset [0]>>>
334 4
334 4
335
335
336 $ log '1 or 2'
336 $ log '1 or 2'
337 1
337 1
338 2
338 2
339 $ log '1|2'
339 $ log '1|2'
340 1
340 1
341 2
341 2
342 $ log '1 and 2'
342 $ log '1 and 2'
343 $ log '1&2'
343 $ log '1&2'
344 $ try '1&2|3' # precedence - and is higher
344 $ try '1&2|3' # precedence - and is higher
345 (or
345 (or
346 (list
346 (list
347 (and
347 (and
348 (symbol '1')
348 (symbol '1')
349 (symbol '2'))
349 (symbol '2'))
350 (symbol '3')))
350 (symbol '3')))
351 * set:
351 * set:
352 <addset
352 <addset
353 <baseset []>,
353 <baseset []>,
354 <baseset [3]>>
354 <baseset [3]>>
355 3
355 3
356 $ try '1|2&3'
356 $ try '1|2&3'
357 (or
357 (or
358 (list
358 (list
359 (symbol '1')
359 (symbol '1')
360 (and
360 (and
361 (symbol '2')
361 (symbol '2')
362 (symbol '3'))))
362 (symbol '3'))))
363 * set:
363 * set:
364 <addset
364 <addset
365 <baseset [1]>,
365 <baseset [1]>,
366 <baseset []>>
366 <baseset []>>
367 1
367 1
368 $ try '1&2&3' # associativity
368 $ try '1&2&3' # associativity
369 (and
369 (and
370 (and
370 (and
371 (symbol '1')
371 (symbol '1')
372 (symbol '2'))
372 (symbol '2'))
373 (symbol '3'))
373 (symbol '3'))
374 * set:
374 * set:
375 <baseset []>
375 <baseset []>
376 $ try '1|(2|3)'
376 $ try '1|(2|3)'
377 (or
377 (or
378 (list
378 (list
379 (symbol '1')
379 (symbol '1')
380 (group
380 (group
381 (or
381 (or
382 (list
382 (list
383 (symbol '2')
383 (symbol '2')
384 (symbol '3'))))))
384 (symbol '3'))))))
385 * set:
385 * set:
386 <addset
386 <addset
387 <baseset [1]>,
387 <baseset [1]>,
388 <baseset [2, 3]>>
388 <baseset [2, 3]>>
389 1
389 1
390 2
390 2
391 3
391 3
392 $ log '1.0' # tag
392 $ log '1.0' # tag
393 6
393 6
394 $ log 'a' # branch
394 $ log 'a' # branch
395 0
395 0
396 $ log '2785f51ee'
396 $ log '2785f51ee'
397 0
397 0
398 $ log 'date(2005)'
398 $ log 'date(2005)'
399 4
399 4
400 $ log 'date(this is a test)'
400 $ log 'date(this is a test)'
401 hg: parse error at 10: unexpected token: symbol
401 hg: parse error at 10: unexpected token: symbol
402 (date(this is a test)
402 (date(this is a test)
403 ^ here)
403 ^ here)
404 [255]
404 [255]
405 $ log 'date()'
405 $ log 'date()'
406 hg: parse error: date requires a string
406 hg: parse error: date requires a string
407 [255]
407 [255]
408 $ log 'date'
408 $ log 'date'
409 abort: unknown revision 'date'!
409 abort: unknown revision 'date'!
410 [255]
410 [255]
411 $ log 'date('
411 $ log 'date('
412 hg: parse error at 5: not a prefix: end
412 hg: parse error at 5: not a prefix: end
413 (date(
413 (date(
414 ^ here)
414 ^ here)
415 [255]
415 [255]
416 $ log 'date("\xy")'
416 $ log 'date("\xy")'
417 hg: parse error: invalid \x escape* (glob)
417 hg: parse error: invalid \x escape* (glob)
418 [255]
418 [255]
419 $ log 'date(tip)'
419 $ log 'date(tip)'
420 hg: parse error: invalid date: 'tip'
420 hg: parse error: invalid date: 'tip'
421 [255]
421 [255]
422 $ log '0:date'
422 $ log '0:date'
423 abort: unknown revision 'date'!
423 abort: unknown revision 'date'!
424 [255]
424 [255]
425 $ log '::"date"'
425 $ log '::"date"'
426 abort: unknown revision 'date'!
426 abort: unknown revision 'date'!
427 [255]
427 [255]
428 $ hg book date -r 4
428 $ hg book date -r 4
429 $ log '0:date'
429 $ log '0:date'
430 0
430 0
431 1
431 1
432 2
432 2
433 3
433 3
434 4
434 4
435 $ log '::date'
435 $ log '::date'
436 0
436 0
437 1
437 1
438 2
438 2
439 4
439 4
440 $ log '::"date"'
440 $ log '::"date"'
441 0
441 0
442 1
442 1
443 2
443 2
444 4
444 4
445 $ log 'date(2005) and 1::'
445 $ log 'date(2005) and 1::'
446 4
446 4
447 $ hg book -d date
447 $ hg book -d date
448
448
449 function name should be a symbol
449 function name should be a symbol
450
450
451 $ log '"date"(2005)'
451 $ log '"date"(2005)'
452 hg: parse error: not a symbol
452 hg: parse error: not a symbol
453 [255]
453 [255]
454
454
455 keyword arguments
455 keyword arguments
456
456
457 $ log 'extra(branch, value=a)'
457 $ log 'extra(branch, value=a)'
458 0
458 0
459
459
460 $ log 'extra(branch, a, b)'
460 $ log 'extra(branch, a, b)'
461 hg: parse error: extra takes at most 2 positional arguments
461 hg: parse error: extra takes at most 2 positional arguments
462 [255]
462 [255]
463 $ log 'extra(a, label=b)'
463 $ log 'extra(a, label=b)'
464 hg: parse error: extra got multiple values for keyword argument 'label'
464 hg: parse error: extra got multiple values for keyword argument 'label'
465 [255]
465 [255]
466 $ log 'extra(label=branch, default)'
466 $ log 'extra(label=branch, default)'
467 hg: parse error: extra got an invalid argument
467 hg: parse error: extra got an invalid argument
468 [255]
468 [255]
469 $ log 'extra(branch, foo+bar=baz)'
469 $ log 'extra(branch, foo+bar=baz)'
470 hg: parse error: extra got an invalid argument
470 hg: parse error: extra got an invalid argument
471 [255]
471 [255]
472 $ log 'extra(unknown=branch)'
472 $ log 'extra(unknown=branch)'
473 hg: parse error: extra got an unexpected keyword argument 'unknown'
473 hg: parse error: extra got an unexpected keyword argument 'unknown'
474 [255]
474 [255]
475 $ log 'extra((), x)'
476 hg: parse error: first argument to extra must be a string
477 [255]
478 $ log 'extra(label=x, ())'
479 hg: parse error: extra got an invalid argument
480 [255]
475
481
476 $ try 'foo=bar|baz'
482 $ try 'foo=bar|baz'
477 (keyvalue
483 (keyvalue
478 (symbol 'foo')
484 (symbol 'foo')
479 (or
485 (or
480 (list
486 (list
481 (symbol 'bar')
487 (symbol 'bar')
482 (symbol 'baz'))))
488 (symbol 'baz'))))
483 hg: parse error: can't use a key-value pair in this context
489 hg: parse error: can't use a key-value pair in this context
484 [255]
490 [255]
485
491
486 right-hand side should be optimized recursively
492 right-hand side should be optimized recursively
487
493
488 $ try --optimize 'foo=(not public())'
494 $ try --optimize 'foo=(not public())'
489 (keyvalue
495 (keyvalue
490 (symbol 'foo')
496 (symbol 'foo')
491 (group
497 (group
492 (not
498 (not
493 (func
499 (func
494 (symbol 'public')
500 (symbol 'public')
495 None))))
501 None))))
496 * optimized:
502 * optimized:
497 (keyvalue
503 (keyvalue
498 (symbol 'foo')
504 (symbol 'foo')
499 (func
505 (func
500 (symbol '_notpublic')
506 (symbol '_notpublic')
501 None))
507 None))
502 hg: parse error: can't use a key-value pair in this context
508 hg: parse error: can't use a key-value pair in this context
503 [255]
509 [255]
504
510
505 relation-subscript operator has the highest binding strength (as function call):
511 relation-subscript operator has the highest binding strength (as function call):
506
512
507 $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
513 $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
508 * parsed:
514 * parsed:
509 (range
515 (range
510 (symbol 'tip')
516 (symbol 'tip')
511 (relsubscript
517 (relsubscript
512 (parentpost
518 (parentpost
513 (symbol 'tip'))
519 (symbol 'tip'))
514 (symbol 'generations')
520 (symbol 'generations')
515 (negate
521 (negate
516 (symbol '1'))))
522 (symbol '1'))))
517 9
523 9
518 8
524 8
519 7
525 7
520 6
526 6
521 5
527 5
522 4
528 4
523
529
524 $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
530 $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
525 * parsed:
531 * parsed:
526 (not
532 (not
527 (relsubscript
533 (relsubscript
528 (func
534 (func
529 (symbol 'public')
535 (symbol 'public')
530 None)
536 None)
531 (symbol 'generations')
537 (symbol 'generations')
532 (symbol '0')))
538 (symbol '0')))
533
539
534 left-hand side of relation-subscript operator should be optimized recursively:
540 left-hand side of relation-subscript operator should be optimized recursively:
535
541
536 $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
542 $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
537 > '(not public())#generations[0]'
543 > '(not public())#generations[0]'
538 * analyzed:
544 * analyzed:
539 (relsubscript
545 (relsubscript
540 (not
546 (not
541 (func
547 (func
542 (symbol 'public')
548 (symbol 'public')
543 None))
549 None))
544 (symbol 'generations')
550 (symbol 'generations')
545 (symbol '0'))
551 (symbol '0'))
546 * optimized:
552 * optimized:
547 (relsubscript
553 (relsubscript
548 (func
554 (func
549 (symbol '_notpublic')
555 (symbol '_notpublic')
550 None)
556 None)
551 (symbol 'generations')
557 (symbol 'generations')
552 (symbol '0'))
558 (symbol '0'))
553
559
554 resolution of subscript and relation-subscript ternary operators:
560 resolution of subscript and relation-subscript ternary operators:
555
561
556 $ hg debugrevspec -p analyzed 'tip[0]'
562 $ hg debugrevspec -p analyzed 'tip[0]'
557 * analyzed:
563 * analyzed:
558 (subscript
564 (subscript
559 (symbol 'tip')
565 (symbol 'tip')
560 (symbol '0'))
566 (symbol '0'))
561 hg: parse error: can't use a subscript in this context
567 hg: parse error: can't use a subscript in this context
562 [255]
568 [255]
563
569
564 $ hg debugrevspec -p analyzed 'tip#rel[0]'
570 $ hg debugrevspec -p analyzed 'tip#rel[0]'
565 * analyzed:
571 * analyzed:
566 (relsubscript
572 (relsubscript
567 (symbol 'tip')
573 (symbol 'tip')
568 (symbol 'rel')
574 (symbol 'rel')
569 (symbol '0'))
575 (symbol '0'))
570 hg: parse error: unknown identifier: rel
576 hg: parse error: unknown identifier: rel
571 [255]
577 [255]
572
578
573 $ hg debugrevspec -p analyzed '(tip#rel)[0]'
579 $ hg debugrevspec -p analyzed '(tip#rel)[0]'
574 * analyzed:
580 * analyzed:
575 (subscript
581 (subscript
576 (relation
582 (relation
577 (symbol 'tip')
583 (symbol 'tip')
578 (symbol 'rel'))
584 (symbol 'rel'))
579 (symbol '0'))
585 (symbol '0'))
580 hg: parse error: can't use a subscript in this context
586 hg: parse error: can't use a subscript in this context
581 [255]
587 [255]
582
588
583 $ hg debugrevspec -p analyzed 'tip#rel[0][1]'
589 $ hg debugrevspec -p analyzed 'tip#rel[0][1]'
584 * analyzed:
590 * analyzed:
585 (subscript
591 (subscript
586 (relsubscript
592 (relsubscript
587 (symbol 'tip')
593 (symbol 'tip')
588 (symbol 'rel')
594 (symbol 'rel')
589 (symbol '0'))
595 (symbol '0'))
590 (symbol '1'))
596 (symbol '1'))
591 hg: parse error: can't use a subscript in this context
597 hg: parse error: can't use a subscript in this context
592 [255]
598 [255]
593
599
594 $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]'
600 $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]'
595 * analyzed:
601 * analyzed:
596 (relsubscript
602 (relsubscript
597 (relation
603 (relation
598 (symbol 'tip')
604 (symbol 'tip')
599 (symbol 'rel0'))
605 (symbol 'rel0'))
600 (symbol 'rel1')
606 (symbol 'rel1')
601 (symbol '1'))
607 (symbol '1'))
602 hg: parse error: unknown identifier: rel1
608 hg: parse error: unknown identifier: rel1
603 [255]
609 [255]
604
610
605 $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]'
611 $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]'
606 * analyzed:
612 * analyzed:
607 (relsubscript
613 (relsubscript
608 (relsubscript
614 (relsubscript
609 (symbol 'tip')
615 (symbol 'tip')
610 (symbol 'rel0')
616 (symbol 'rel0')
611 (symbol '0'))
617 (symbol '0'))
612 (symbol 'rel1')
618 (symbol 'rel1')
613 (symbol '1'))
619 (symbol '1'))
614 hg: parse error: unknown identifier: rel1
620 hg: parse error: unknown identifier: rel1
615 [255]
621 [255]
616
622
617 parse errors of relation, subscript and relation-subscript operators:
623 parse errors of relation, subscript and relation-subscript operators:
618
624
619 $ hg debugrevspec '[0]'
625 $ hg debugrevspec '[0]'
620 hg: parse error at 0: not a prefix: [
626 hg: parse error at 0: not a prefix: [
621 ([0]
627 ([0]
622 ^ here)
628 ^ here)
623 [255]
629 [255]
624 $ hg debugrevspec '.#'
630 $ hg debugrevspec '.#'
625 hg: parse error at 2: not a prefix: end
631 hg: parse error at 2: not a prefix: end
626 (.#
632 (.#
627 ^ here)
633 ^ here)
628 [255]
634 [255]
629 $ hg debugrevspec '#rel'
635 $ hg debugrevspec '#rel'
630 hg: parse error at 0: not a prefix: #
636 hg: parse error at 0: not a prefix: #
631 (#rel
637 (#rel
632 ^ here)
638 ^ here)
633 [255]
639 [255]
634 $ hg debugrevspec '.#rel[0'
640 $ hg debugrevspec '.#rel[0'
635 hg: parse error at 7: unexpected token: end
641 hg: parse error at 7: unexpected token: end
636 (.#rel[0
642 (.#rel[0
637 ^ here)
643 ^ here)
638 [255]
644 [255]
639 $ hg debugrevspec '.]'
645 $ hg debugrevspec '.]'
640 hg: parse error at 1: invalid token
646 hg: parse error at 1: invalid token
641 (.]
647 (.]
642 ^ here)
648 ^ here)
643 [255]
649 [255]
644
650
645 $ hg debugrevspec '.#generations[a]'
651 $ hg debugrevspec '.#generations[a]'
646 hg: parse error: relation subscript must be an integer or a range
652 hg: parse error: relation subscript must be an integer or a range
647 [255]
653 [255]
648 $ hg debugrevspec '.#generations[1-2]'
654 $ hg debugrevspec '.#generations[1-2]'
649 hg: parse error: relation subscript must be an integer or a range
655 hg: parse error: relation subscript must be an integer or a range
650 [255]
656 [255]
651 $ hg debugrevspec '.#generations[foo:bar]'
657 $ hg debugrevspec '.#generations[foo:bar]'
652 hg: parse error: relation subscript bounds must be integers
658 hg: parse error: relation subscript bounds must be integers
653 [255]
659 [255]
654
660
655 suggested relations
661 suggested relations
656
662
657 $ hg debugrevspec '.#generafions[0]'
663 $ hg debugrevspec '.#generafions[0]'
658 hg: parse error: unknown identifier: generafions
664 hg: parse error: unknown identifier: generafions
659 (did you mean generations?)
665 (did you mean generations?)
660 [255]
666 [255]
661
667
662 $ hg debugrevspec '.#f[0]'
668 $ hg debugrevspec '.#f[0]'
663 hg: parse error: unknown identifier: f
669 hg: parse error: unknown identifier: f
664 [255]
670 [255]
665
671
666 parsed tree at stages:
672 parsed tree at stages:
667
673
668 $ hg debugrevspec -p all '()'
674 $ hg debugrevspec -p all '()'
669 * parsed:
675 * parsed:
670 (group
676 (group
671 None)
677 None)
672 * expanded:
678 * expanded:
673 (group
679 (group
674 None)
680 None)
675 * concatenated:
681 * concatenated:
676 (group
682 (group
677 None)
683 None)
678 * analyzed:
684 * analyzed:
679 None
685 None
680 * optimized:
686 * optimized:
681 None
687 None
682 hg: parse error: missing argument
688 hg: parse error: missing argument
683 [255]
689 [255]
684
690
685 $ hg debugrevspec --no-optimized -p all '()'
691 $ hg debugrevspec --no-optimized -p all '()'
686 * parsed:
692 * parsed:
687 (group
693 (group
688 None)
694 None)
689 * expanded:
695 * expanded:
690 (group
696 (group
691 None)
697 None)
692 * concatenated:
698 * concatenated:
693 (group
699 (group
694 None)
700 None)
695 * analyzed:
701 * analyzed:
696 None
702 None
697 hg: parse error: missing argument
703 hg: parse error: missing argument
698 [255]
704 [255]
699
705
700 $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
706 $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
701 * parsed:
707 * parsed:
702 (minus
708 (minus
703 (group
709 (group
704 (or
710 (or
705 (list
711 (list
706 (symbol '0')
712 (symbol '0')
707 (symbol '1'))))
713 (symbol '1'))))
708 (symbol '1'))
714 (symbol '1'))
709 * analyzed:
715 * analyzed:
710 (and
716 (and
711 (or
717 (or
712 (list
718 (list
713 (symbol '0')
719 (symbol '0')
714 (symbol '1')))
720 (symbol '1')))
715 (not
721 (not
716 (symbol '1')))
722 (symbol '1')))
717 * optimized:
723 * optimized:
718 (difference
724 (difference
719 (func
725 (func
720 (symbol '_list')
726 (symbol '_list')
721 (string '0\x001'))
727 (string '0\x001'))
722 (symbol '1'))
728 (symbol '1'))
723 0
729 0
724
730
725 $ hg debugrevspec -p unknown '0'
731 $ hg debugrevspec -p unknown '0'
726 abort: invalid stage name: unknown
732 abort: invalid stage name: unknown
727 [255]
733 [255]
728
734
729 $ hg debugrevspec -p all --optimize '0'
735 $ hg debugrevspec -p all --optimize '0'
730 abort: cannot use --optimize with --show-stage
736 abort: cannot use --optimize with --show-stage
731 [255]
737 [255]
732
738
733 verify optimized tree:
739 verify optimized tree:
734
740
735 $ hg debugrevspec --verify '0|1'
741 $ hg debugrevspec --verify '0|1'
736
742
737 $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
743 $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
738 * analyzed:
744 * analyzed:
739 (and
745 (and
740 (func
746 (func
741 (symbol 'r3232')
747 (symbol 'r3232')
742 None)
748 None)
743 (symbol '2'))
749 (symbol '2'))
744 * optimized:
750 * optimized:
745 (andsmally
751 (andsmally
746 (func
752 (func
747 (symbol 'r3232')
753 (symbol 'r3232')
748 None)
754 None)
749 (symbol '2'))
755 (symbol '2'))
750 * analyzed set:
756 * analyzed set:
751 <baseset [2]>
757 <baseset [2]>
752 * optimized set:
758 * optimized set:
753 <baseset [2, 2]>
759 <baseset [2, 2]>
754 --- analyzed
760 --- analyzed
755 +++ optimized
761 +++ optimized
756 2
762 2
757 +2
763 +2
758 [1]
764 [1]
759
765
760 $ hg debugrevspec --no-optimized --verify-optimized '0'
766 $ hg debugrevspec --no-optimized --verify-optimized '0'
761 abort: cannot use --verify-optimized with --no-optimized
767 abort: cannot use --verify-optimized with --no-optimized
762 [255]
768 [255]
763
769
764 Test that symbols only get parsed as functions if there's an opening
770 Test that symbols only get parsed as functions if there's an opening
765 parenthesis.
771 parenthesis.
766
772
767 $ hg book only -r 9
773 $ hg book only -r 9
768 $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark
774 $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark
769 8
775 8
770 9
776 9
771
777
772 ':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
778 ':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
773 may be hidden (issue5385)
779 may be hidden (issue5385)
774
780
775 $ try -p parsed -p analyzed ':'
781 $ try -p parsed -p analyzed ':'
776 * parsed:
782 * parsed:
777 (rangeall
783 (rangeall
778 None)
784 None)
779 * analyzed:
785 * analyzed:
780 (rangeall
786 (rangeall
781 None)
787 None)
782 * set:
788 * set:
783 <spanset+ 0:10>
789 <spanset+ 0:10>
784 0
790 0
785 1
791 1
786 2
792 2
787 3
793 3
788 4
794 4
789 5
795 5
790 6
796 6
791 7
797 7
792 8
798 8
793 9
799 9
794 $ try -p analyzed ':1'
800 $ try -p analyzed ':1'
795 * analyzed:
801 * analyzed:
796 (rangepre
802 (rangepre
797 (symbol '1'))
803 (symbol '1'))
798 * set:
804 * set:
799 <spanset+ 0:2>
805 <spanset+ 0:2>
800 0
806 0
801 1
807 1
802 $ try -p analyzed ':(1|2)'
808 $ try -p analyzed ':(1|2)'
803 * analyzed:
809 * analyzed:
804 (rangepre
810 (rangepre
805 (or
811 (or
806 (list
812 (list
807 (symbol '1')
813 (symbol '1')
808 (symbol '2'))))
814 (symbol '2'))))
809 * set:
815 * set:
810 <spanset+ 0:3>
816 <spanset+ 0:3>
811 0
817 0
812 1
818 1
813 2
819 2
814 $ try -p analyzed ':(1&2)'
820 $ try -p analyzed ':(1&2)'
815 * analyzed:
821 * analyzed:
816 (rangepre
822 (rangepre
817 (and
823 (and
818 (symbol '1')
824 (symbol '1')
819 (symbol '2')))
825 (symbol '2')))
820 * set:
826 * set:
821 <baseset []>
827 <baseset []>
822
828
823 infix/suffix resolution of ^ operator (issue2884, issue5764):
829 infix/suffix resolution of ^ operator (issue2884, issue5764):
824
830
825 x^:y means (x^):y
831 x^:y means (x^):y
826
832
827 $ try '1^:2'
833 $ try '1^:2'
828 (range
834 (range
829 (parentpost
835 (parentpost
830 (symbol '1'))
836 (symbol '1'))
831 (symbol '2'))
837 (symbol '2'))
832 * set:
838 * set:
833 <spanset+ 0:3>
839 <spanset+ 0:3>
834 0
840 0
835 1
841 1
836 2
842 2
837
843
838 $ try '1^::2'
844 $ try '1^::2'
839 (dagrange
845 (dagrange
840 (parentpost
846 (parentpost
841 (symbol '1'))
847 (symbol '1'))
842 (symbol '2'))
848 (symbol '2'))
843 * set:
849 * set:
844 <baseset+ [0, 1, 2]>
850 <baseset+ [0, 1, 2]>
845 0
851 0
846 1
852 1
847 2
853 2
848
854
849 $ try '1^..2'
855 $ try '1^..2'
850 (dagrange
856 (dagrange
851 (parentpost
857 (parentpost
852 (symbol '1'))
858 (symbol '1'))
853 (symbol '2'))
859 (symbol '2'))
854 * set:
860 * set:
855 <baseset+ [0, 1, 2]>
861 <baseset+ [0, 1, 2]>
856 0
862 0
857 1
863 1
858 2
864 2
859
865
860 $ try '9^:'
866 $ try '9^:'
861 (rangepost
867 (rangepost
862 (parentpost
868 (parentpost
863 (symbol '9')))
869 (symbol '9')))
864 * set:
870 * set:
865 <spanset+ 8:10>
871 <spanset+ 8:10>
866 8
872 8
867 9
873 9
868
874
869 $ try '9^::'
875 $ try '9^::'
870 (dagrangepost
876 (dagrangepost
871 (parentpost
877 (parentpost
872 (symbol '9')))
878 (symbol '9')))
873 * set:
879 * set:
874 <generatorsetasc+>
880 <generatorsetasc+>
875 8
881 8
876 9
882 9
877
883
878 $ try '9^..'
884 $ try '9^..'
879 (dagrangepost
885 (dagrangepost
880 (parentpost
886 (parentpost
881 (symbol '9')))
887 (symbol '9')))
882 * set:
888 * set:
883 <generatorsetasc+>
889 <generatorsetasc+>
884 8
890 8
885 9
891 9
886
892
887 x^:y should be resolved before omitting group operators
893 x^:y should be resolved before omitting group operators
888
894
889 $ try '1^(:2)'
895 $ try '1^(:2)'
890 (parent
896 (parent
891 (symbol '1')
897 (symbol '1')
892 (group
898 (group
893 (rangepre
899 (rangepre
894 (symbol '2'))))
900 (symbol '2'))))
895 hg: parse error: ^ expects a number 0, 1, or 2
901 hg: parse error: ^ expects a number 0, 1, or 2
896 [255]
902 [255]
897
903
898 x^:y should be resolved recursively
904 x^:y should be resolved recursively
899
905
900 $ try 'sort(1^:2)'
906 $ try 'sort(1^:2)'
901 (func
907 (func
902 (symbol 'sort')
908 (symbol 'sort')
903 (range
909 (range
904 (parentpost
910 (parentpost
905 (symbol '1'))
911 (symbol '1'))
906 (symbol '2')))
912 (symbol '2')))
907 * set:
913 * set:
908 <spanset+ 0:3>
914 <spanset+ 0:3>
909 0
915 0
910 1
916 1
911 2
917 2
912
918
913 $ try '(3^:4)^:2'
919 $ try '(3^:4)^:2'
914 (range
920 (range
915 (parentpost
921 (parentpost
916 (group
922 (group
917 (range
923 (range
918 (parentpost
924 (parentpost
919 (symbol '3'))
925 (symbol '3'))
920 (symbol '4'))))
926 (symbol '4'))))
921 (symbol '2'))
927 (symbol '2'))
922 * set:
928 * set:
923 <spanset+ 0:3>
929 <spanset+ 0:3>
924 0
930 0
925 1
931 1
926 2
932 2
927
933
928 $ try '(3^::4)^::2'
934 $ try '(3^::4)^::2'
929 (dagrange
935 (dagrange
930 (parentpost
936 (parentpost
931 (group
937 (group
932 (dagrange
938 (dagrange
933 (parentpost
939 (parentpost
934 (symbol '3'))
940 (symbol '3'))
935 (symbol '4'))))
941 (symbol '4'))))
936 (symbol '2'))
942 (symbol '2'))
937 * set:
943 * set:
938 <baseset+ [0, 1, 2]>
944 <baseset+ [0, 1, 2]>
939 0
945 0
940 1
946 1
941 2
947 2
942
948
943 $ try '(9^:)^:'
949 $ try '(9^:)^:'
944 (rangepost
950 (rangepost
945 (parentpost
951 (parentpost
946 (group
952 (group
947 (rangepost
953 (rangepost
948 (parentpost
954 (parentpost
949 (symbol '9'))))))
955 (symbol '9'))))))
950 * set:
956 * set:
951 <spanset+ 4:10>
957 <spanset+ 4:10>
952 4
958 4
953 5
959 5
954 6
960 6
955 7
961 7
956 8
962 8
957 9
963 9
958
964
959 x^ in alias should also be resolved
965 x^ in alias should also be resolved
960
966
961 $ try 'A' --config 'revsetalias.A=1^:2'
967 $ try 'A' --config 'revsetalias.A=1^:2'
962 (symbol 'A')
968 (symbol 'A')
963 * expanded:
969 * expanded:
964 (range
970 (range
965 (parentpost
971 (parentpost
966 (symbol '1'))
972 (symbol '1'))
967 (symbol '2'))
973 (symbol '2'))
968 * set:
974 * set:
969 <spanset+ 0:3>
975 <spanset+ 0:3>
970 0
976 0
971 1
977 1
972 2
978 2
973
979
974 $ try 'A:2' --config 'revsetalias.A=1^'
980 $ try 'A:2' --config 'revsetalias.A=1^'
975 (range
981 (range
976 (symbol 'A')
982 (symbol 'A')
977 (symbol '2'))
983 (symbol '2'))
978 * expanded:
984 * expanded:
979 (range
985 (range
980 (parentpost
986 (parentpost
981 (symbol '1'))
987 (symbol '1'))
982 (symbol '2'))
988 (symbol '2'))
983 * set:
989 * set:
984 <spanset+ 0:3>
990 <spanset+ 0:3>
985 0
991 0
986 1
992 1
987 2
993 2
988
994
989 but not beyond the boundary of alias expansion, because the resolution should
995 but not beyond the boundary of alias expansion, because the resolution should
990 be made at the parsing stage
996 be made at the parsing stage
991
997
992 $ try '1^A' --config 'revsetalias.A=:2'
998 $ try '1^A' --config 'revsetalias.A=:2'
993 (parent
999 (parent
994 (symbol '1')
1000 (symbol '1')
995 (symbol 'A'))
1001 (symbol 'A'))
996 * expanded:
1002 * expanded:
997 (parent
1003 (parent
998 (symbol '1')
1004 (symbol '1')
999 (rangepre
1005 (rangepre
1000 (symbol '2')))
1006 (symbol '2')))
1001 hg: parse error: ^ expects a number 0, 1, or 2
1007 hg: parse error: ^ expects a number 0, 1, or 2
1002 [255]
1008 [255]
1003
1009
1004 '::' itself isn't a valid expression
1010 '::' itself isn't a valid expression
1005
1011
1006 $ try '::'
1012 $ try '::'
1007 (dagrangeall
1013 (dagrangeall
1008 None)
1014 None)
1009 hg: parse error: can't use '::' in this context
1015 hg: parse error: can't use '::' in this context
1010 [255]
1016 [255]
1011
1017
1012 ancestor can accept 0 or more arguments
1018 ancestor can accept 0 or more arguments
1013
1019
1014 $ log 'ancestor()'
1020 $ log 'ancestor()'
1015 $ log 'ancestor(1)'
1021 $ log 'ancestor(1)'
1016 1
1022 1
1017 $ log 'ancestor(4,5)'
1023 $ log 'ancestor(4,5)'
1018 1
1024 1
1019 $ log 'ancestor(4,5) and 4'
1025 $ log 'ancestor(4,5) and 4'
1020 $ log 'ancestor(0,0,1,3)'
1026 $ log 'ancestor(0,0,1,3)'
1021 0
1027 0
1022 $ log 'ancestor(3,1,5,3,5,1)'
1028 $ log 'ancestor(3,1,5,3,5,1)'
1023 1
1029 1
1024 $ log 'ancestor(0,1,3,5)'
1030 $ log 'ancestor(0,1,3,5)'
1025 0
1031 0
1026 $ log 'ancestor(1,2,3,4,5)'
1032 $ log 'ancestor(1,2,3,4,5)'
1027 1
1033 1
1028
1034
1029 test ancestors
1035 test ancestors
1030
1036
1031 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1037 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1032 @ 9
1038 @ 9
1033 o 8
1039 o 8
1034 | o 7
1040 | o 7
1035 | o 6
1041 | o 6
1036 |/|
1042 |/|
1037 | o 5
1043 | o 5
1038 o | 4
1044 o | 4
1039 | o 3
1045 | o 3
1040 o | 2
1046 o | 2
1041 |/
1047 |/
1042 o 1
1048 o 1
1043 o 0
1049 o 0
1044
1050
1045 $ log 'ancestors(5)'
1051 $ log 'ancestors(5)'
1046 0
1052 0
1047 1
1053 1
1048 3
1054 3
1049 5
1055 5
1050 $ log 'ancestor(ancestors(5))'
1056 $ log 'ancestor(ancestors(5))'
1051 0
1057 0
1052 $ log '::r3232()'
1058 $ log '::r3232()'
1053 0
1059 0
1054 1
1060 1
1055 2
1061 2
1056 3
1062 3
1057
1063
1058 test common ancestors
1064 test common ancestors
1059
1065
1060 $ hg log -T '{rev}\n' -r 'commonancestors(7 + 9)'
1066 $ hg log -T '{rev}\n' -r 'commonancestors(7 + 9)'
1061 0
1067 0
1062 1
1068 1
1063 2
1069 2
1064 4
1070 4
1065
1071
1066 $ hg log -T '{rev}\n' -r 'commonancestors(heads(all()))'
1072 $ hg log -T '{rev}\n' -r 'commonancestors(heads(all()))'
1067 0
1073 0
1068 1
1074 1
1069 2
1075 2
1070 4
1076 4
1071
1077
1072 $ hg log -T '{rev}\n' -r 'commonancestors(9)'
1078 $ hg log -T '{rev}\n' -r 'commonancestors(9)'
1073 0
1079 0
1074 1
1080 1
1075 2
1081 2
1076 4
1082 4
1077 8
1083 8
1078 9
1084 9
1079
1085
1080 $ hg log -T '{rev}\n' -r 'commonancestors(8 + 9)'
1086 $ hg log -T '{rev}\n' -r 'commonancestors(8 + 9)'
1081 0
1087 0
1082 1
1088 1
1083 2
1089 2
1084 4
1090 4
1085 8
1091 8
1086
1092
1087 test the specialized implementation of heads(commonancestors(..))
1093 test the specialized implementation of heads(commonancestors(..))
1088 (2 gcas is tested in test-merge-criss-cross.t)
1094 (2 gcas is tested in test-merge-criss-cross.t)
1089
1095
1090 $ hg log -T '{rev}\n' -r 'heads(commonancestors(7 + 9))'
1096 $ hg log -T '{rev}\n' -r 'heads(commonancestors(7 + 9))'
1091 4
1097 4
1092 $ hg log -T '{rev}\n' -r 'heads(commonancestors(heads(all())))'
1098 $ hg log -T '{rev}\n' -r 'heads(commonancestors(heads(all())))'
1093 4
1099 4
1094 $ hg log -T '{rev}\n' -r 'heads(commonancestors(9))'
1100 $ hg log -T '{rev}\n' -r 'heads(commonancestors(9))'
1095 9
1101 9
1096 $ hg log -T '{rev}\n' -r 'heads(commonancestors(8 + 9))'
1102 $ hg log -T '{rev}\n' -r 'heads(commonancestors(8 + 9))'
1097 8
1103 8
1098
1104
1099 test ancestor variants of empty revision
1105 test ancestor variants of empty revision
1100
1106
1101 $ log 'ancestor(none())'
1107 $ log 'ancestor(none())'
1102 $ log 'ancestors(none())'
1108 $ log 'ancestors(none())'
1103 $ log 'commonancestors(none())'
1109 $ log 'commonancestors(none())'
1104 $ log 'heads(commonancestors(none()))'
1110 $ log 'heads(commonancestors(none()))'
1105
1111
1106 test ancestors with depth limit
1112 test ancestors with depth limit
1107
1113
1108 (depth=0 selects the node itself)
1114 (depth=0 selects the node itself)
1109
1115
1110 $ log 'reverse(ancestors(9, depth=0))'
1116 $ log 'reverse(ancestors(9, depth=0))'
1111 9
1117 9
1112
1118
1113 (interleaved: '4' would be missing if heap queue were higher depth first)
1119 (interleaved: '4' would be missing if heap queue were higher depth first)
1114
1120
1115 $ log 'reverse(ancestors(8:9, depth=1))'
1121 $ log 'reverse(ancestors(8:9, depth=1))'
1116 9
1122 9
1117 8
1123 8
1118 4
1124 4
1119
1125
1120 (interleaved: '2' would be missing if heap queue were higher depth first)
1126 (interleaved: '2' would be missing if heap queue were higher depth first)
1121
1127
1122 $ log 'reverse(ancestors(7+8, depth=2))'
1128 $ log 'reverse(ancestors(7+8, depth=2))'
1123 8
1129 8
1124 7
1130 7
1125 6
1131 6
1126 5
1132 5
1127 4
1133 4
1128 2
1134 2
1129
1135
1130 (walk example above by separate queries)
1136 (walk example above by separate queries)
1131
1137
1132 $ log 'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))'
1138 $ log 'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))'
1133 8
1139 8
1134 4
1140 4
1135 2
1141 2
1136 7
1142 7
1137 6
1143 6
1138 5
1144 5
1139
1145
1140 (walk 2nd and 3rd ancestors)
1146 (walk 2nd and 3rd ancestors)
1141
1147
1142 $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
1148 $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
1143 5
1149 5
1144 4
1150 4
1145 3
1151 3
1146 2
1152 2
1147
1153
1148 (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
1154 (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
1149
1155
1150 $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
1156 $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
1151 5
1157 5
1152 4
1158 4
1153 2
1159 2
1154
1160
1155 (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
1161 (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
1156 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
1162 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
1157 multiple depths)
1163 multiple depths)
1158
1164
1159 $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
1165 $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
1160 5
1166 5
1161 2
1167 2
1162
1168
1163 test bad arguments passed to ancestors()
1169 test bad arguments passed to ancestors()
1164
1170
1165 $ log 'ancestors(., depth=-1)'
1171 $ log 'ancestors(., depth=-1)'
1166 hg: parse error: negative depth
1172 hg: parse error: negative depth
1167 [255]
1173 [255]
1168 $ log 'ancestors(., depth=foo)'
1174 $ log 'ancestors(., depth=foo)'
1169 hg: parse error: ancestors expects an integer depth
1175 hg: parse error: ancestors expects an integer depth
1170 [255]
1176 [255]
1171
1177
1172 test descendants
1178 test descendants
1173
1179
1174 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1180 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1175 @ 9
1181 @ 9
1176 o 8
1182 o 8
1177 | o 7
1183 | o 7
1178 | o 6
1184 | o 6
1179 |/|
1185 |/|
1180 | o 5
1186 | o 5
1181 o | 4
1187 o | 4
1182 | o 3
1188 | o 3
1183 o | 2
1189 o | 2
1184 |/
1190 |/
1185 o 1
1191 o 1
1186 o 0
1192 o 0
1187
1193
1188 (null is ultimate root and has optimized path)
1194 (null is ultimate root and has optimized path)
1189
1195
1190 $ log 'null:4 & descendants(null)'
1196 $ log 'null:4 & descendants(null)'
1191 -1
1197 -1
1192 0
1198 0
1193 1
1199 1
1194 2
1200 2
1195 3
1201 3
1196 4
1202 4
1197
1203
1198 (including merge)
1204 (including merge)
1199
1205
1200 $ log ':8 & descendants(2)'
1206 $ log ':8 & descendants(2)'
1201 2
1207 2
1202 4
1208 4
1203 6
1209 6
1204 7
1210 7
1205 8
1211 8
1206
1212
1207 (multiple roots)
1213 (multiple roots)
1208
1214
1209 $ log ':8 & descendants(2+5)'
1215 $ log ':8 & descendants(2+5)'
1210 2
1216 2
1211 4
1217 4
1212 5
1218 5
1213 6
1219 6
1214 7
1220 7
1215 8
1221 8
1216
1222
1217 test descendants with depth limit
1223 test descendants with depth limit
1218
1224
1219 (depth=0 selects the node itself)
1225 (depth=0 selects the node itself)
1220
1226
1221 $ log 'descendants(0, depth=0)'
1227 $ log 'descendants(0, depth=0)'
1222 0
1228 0
1223 $ log 'null: & descendants(null, depth=0)'
1229 $ log 'null: & descendants(null, depth=0)'
1224 -1
1230 -1
1225
1231
1226 (p2 = null should be ignored)
1232 (p2 = null should be ignored)
1227
1233
1228 $ log 'null: & descendants(null, depth=2)'
1234 $ log 'null: & descendants(null, depth=2)'
1229 -1
1235 -1
1230 0
1236 0
1231 1
1237 1
1232
1238
1233 (multiple paths: depth(6) = (2, 3))
1239 (multiple paths: depth(6) = (2, 3))
1234
1240
1235 $ log 'descendants(1+3, depth=2)'
1241 $ log 'descendants(1+3, depth=2)'
1236 1
1242 1
1237 2
1243 2
1238 3
1244 3
1239 4
1245 4
1240 5
1246 5
1241 6
1247 6
1242
1248
1243 (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
1249 (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
1244
1250
1245 $ log 'descendants(3+1, depth=2, startdepth=2)'
1251 $ log 'descendants(3+1, depth=2, startdepth=2)'
1246 4
1252 4
1247 5
1253 5
1248 6
1254 6
1249
1255
1250 (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
1256 (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
1251
1257
1252 $ log 'descendants(0+3+6, depth=3, startdepth=1)'
1258 $ log 'descendants(0+3+6, depth=3, startdepth=1)'
1253 1
1259 1
1254 2
1260 2
1255 3
1261 3
1256 4
1262 4
1257 5
1263 5
1258 6
1264 6
1259 7
1265 7
1260
1266
1261 (multiple depths: depth(6) = (0, 4), no match)
1267 (multiple depths: depth(6) = (0, 4), no match)
1262
1268
1263 $ log 'descendants(0+6, depth=3, startdepth=1)'
1269 $ log 'descendants(0+6, depth=3, startdepth=1)'
1264 1
1270 1
1265 2
1271 2
1266 3
1272 3
1267 4
1273 4
1268 5
1274 5
1269 7
1275 7
1270
1276
1271 test ancestors/descendants relation subscript:
1277 test ancestors/descendants relation subscript:
1272
1278
1273 $ log 'tip#generations[0]'
1279 $ log 'tip#generations[0]'
1274 9
1280 9
1275 $ log '.#generations[-1]'
1281 $ log '.#generations[-1]'
1276 8
1282 8
1277 $ log '.#g[(-1)]'
1283 $ log '.#g[(-1)]'
1278 8
1284 8
1279
1285
1280 $ log '6#generations[0:1]'
1286 $ log '6#generations[0:1]'
1281 6
1287 6
1282 7
1288 7
1283 $ log '6#generations[-1:1]'
1289 $ log '6#generations[-1:1]'
1284 4
1290 4
1285 5
1291 5
1286 6
1292 6
1287 7
1293 7
1288 $ log '6#generations[0:]'
1294 $ log '6#generations[0:]'
1289 6
1295 6
1290 7
1296 7
1291 $ log '5#generations[:0]'
1297 $ log '5#generations[:0]'
1292 0
1298 0
1293 1
1299 1
1294 3
1300 3
1295 5
1301 5
1296 $ log '3#generations[:]'
1302 $ log '3#generations[:]'
1297 0
1303 0
1298 1
1304 1
1299 3
1305 3
1300 5
1306 5
1301 6
1307 6
1302 7
1308 7
1303 $ log 'tip#generations[1:-1]'
1309 $ log 'tip#generations[1:-1]'
1304
1310
1305 $ hg debugrevspec -p parsed 'roots(:)#g[2]'
1311 $ hg debugrevspec -p parsed 'roots(:)#g[2]'
1306 * parsed:
1312 * parsed:
1307 (relsubscript
1313 (relsubscript
1308 (func
1314 (func
1309 (symbol 'roots')
1315 (symbol 'roots')
1310 (rangeall
1316 (rangeall
1311 None))
1317 None))
1312 (symbol 'g')
1318 (symbol 'g')
1313 (symbol '2'))
1319 (symbol '2'))
1314 2
1320 2
1315 3
1321 3
1316
1322
1317 test author
1323 test author
1318
1324
1319 $ log 'author(bob)'
1325 $ log 'author(bob)'
1320 2
1326 2
1321 $ log 'author("re:bob|test")'
1327 $ log 'author("re:bob|test")'
1322 0
1328 0
1323 1
1329 1
1324 2
1330 2
1325 3
1331 3
1326 4
1332 4
1327 5
1333 5
1328 6
1334 6
1329 7
1335 7
1330 8
1336 8
1331 9
1337 9
1332 $ log 'author(r"re:\S")'
1338 $ log 'author(r"re:\S")'
1333 0
1339 0
1334 1
1340 1
1335 2
1341 2
1336 3
1342 3
1337 4
1343 4
1338 5
1344 5
1339 6
1345 6
1340 7
1346 7
1341 8
1347 8
1342 9
1348 9
1343 $ log 'branch(Γ©)'
1349 $ log 'branch(Γ©)'
1344 8
1350 8
1345 9
1351 9
1346 $ log 'branch(a)'
1352 $ log 'branch(a)'
1347 0
1353 0
1348 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
1354 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
1349 0 a
1355 0 a
1350 2 a-b-c-
1356 2 a-b-c-
1351 3 +a+b+c+
1357 3 +a+b+c+
1352 4 -a-b-c-
1358 4 -a-b-c-
1353 5 !a/b/c/
1359 5 !a/b/c/
1354 6 _a_b_c_
1360 6 _a_b_c_
1355 7 .a.b.c.
1361 7 .a.b.c.
1356 $ log 'children(ancestor(4,5))'
1362 $ log 'children(ancestor(4,5))'
1357 2
1363 2
1358 3
1364 3
1359
1365
1360 $ log 'children(4)'
1366 $ log 'children(4)'
1361 6
1367 6
1362 8
1368 8
1363 $ log 'children(null)'
1369 $ log 'children(null)'
1364 0
1370 0
1365
1371
1366 $ log 'closed()'
1372 $ log 'closed()'
1367 $ log 'contains(a)'
1373 $ log 'contains(a)'
1368 0
1374 0
1369 1
1375 1
1370 3
1376 3
1371 5
1377 5
1372 $ log 'contains("../repo/a")'
1378 $ log 'contains("../repo/a")'
1373 0
1379 0
1374 1
1380 1
1375 3
1381 3
1376 5
1382 5
1377 $ log 'desc(B)'
1383 $ log 'desc(B)'
1378 5
1384 5
1379 $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
1385 $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
1380 5 5 bug
1386 5 5 bug
1381 6 6 issue619
1387 6 6 issue619
1382 $ log 'descendants(2 or 3)'
1388 $ log 'descendants(2 or 3)'
1383 2
1389 2
1384 3
1390 3
1385 4
1391 4
1386 5
1392 5
1387 6
1393 6
1388 7
1394 7
1389 8
1395 8
1390 9
1396 9
1391 $ log 'file("b*")'
1397 $ log 'file("b*")'
1392 1
1398 1
1393 4
1399 4
1394 $ log 'filelog("b")'
1400 $ log 'filelog("b")'
1395 1
1401 1
1396 4
1402 4
1397 $ log 'filelog("../repo/b")'
1403 $ log 'filelog("../repo/b")'
1398 1
1404 1
1399 4
1405 4
1400 $ log 'follow()'
1406 $ log 'follow()'
1401 0
1407 0
1402 1
1408 1
1403 2
1409 2
1404 4
1410 4
1405 8
1411 8
1406 9
1412 9
1407 $ log 'grep("issue\d+")'
1413 $ log 'grep("issue\d+")'
1408 6
1414 6
1409 $ try 'grep("(")' # invalid regular expression
1415 $ try 'grep("(")' # invalid regular expression
1410 (func
1416 (func
1411 (symbol 'grep')
1417 (symbol 'grep')
1412 (string '('))
1418 (string '('))
1413 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \),.*) (re)
1419 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \),.*) (re)
1414 [255]
1420 [255]
1415 $ try 'grep("\bissue\d+")'
1421 $ try 'grep("\bissue\d+")'
1416 (func
1422 (func
1417 (symbol 'grep')
1423 (symbol 'grep')
1418 (string '\x08issue\\d+'))
1424 (string '\x08issue\\d+'))
1419 * set:
1425 * set:
1420 <filteredset
1426 <filteredset
1421 <fullreposet+ 0:10>,
1427 <fullreposet+ 0:10>,
1422 <grep '\x08issue\\d+'>>
1428 <grep '\x08issue\\d+'>>
1423 $ try 'grep(r"\bissue\d+")'
1429 $ try 'grep(r"\bissue\d+")'
1424 (func
1430 (func
1425 (symbol 'grep')
1431 (symbol 'grep')
1426 (string '\\bissue\\d+'))
1432 (string '\\bissue\\d+'))
1427 * set:
1433 * set:
1428 <filteredset
1434 <filteredset
1429 <fullreposet+ 0:10>,
1435 <fullreposet+ 0:10>,
1430 <grep '\\bissue\\d+'>>
1436 <grep '\\bissue\\d+'>>
1431 6
1437 6
1432 $ try 'grep(r"\")'
1438 $ try 'grep(r"\")'
1433 hg: parse error at 7: unterminated string
1439 hg: parse error at 7: unterminated string
1434 (grep(r"\")
1440 (grep(r"\")
1435 ^ here)
1441 ^ here)
1436 [255]
1442 [255]
1437 $ log 'head()'
1443 $ log 'head()'
1438 0
1444 0
1439 1
1445 1
1440 2
1446 2
1441 3
1447 3
1442 4
1448 4
1443 5
1449 5
1444 6
1450 6
1445 7
1451 7
1446 9
1452 9
1447
1453
1448 Test heads
1454 Test heads
1449
1455
1450 $ log 'heads(6::)'
1456 $ log 'heads(6::)'
1451 7
1457 7
1452
1458
1453 heads() can be computed in subset '9:'
1459 heads() can be computed in subset '9:'
1454
1460
1455 $ hg debugrevspec -s '9: & heads(all())'
1461 $ hg debugrevspec -s '9: & heads(all())'
1456 * set:
1462 * set:
1457 <filteredset
1463 <filteredset
1458 <baseset [9]>,
1464 <baseset [9]>,
1459 <baseset+ [7, 9]>>
1465 <baseset+ [7, 9]>>
1460 9
1466 9
1461
1467
1462 but should follow the order of the subset
1468 but should follow the order of the subset
1463
1469
1464 $ log 'heads(all())'
1470 $ log 'heads(all())'
1465 7
1471 7
1466 9
1472 9
1467 $ log 'heads(tip:0)'
1473 $ log 'heads(tip:0)'
1468 7
1474 7
1469 9
1475 9
1470 $ log 'tip:0 & heads(all())'
1476 $ log 'tip:0 & heads(all())'
1471 9
1477 9
1472 7
1478 7
1473 $ log 'tip:0 & heads(0:tip)'
1479 $ log 'tip:0 & heads(0:tip)'
1474 9
1480 9
1475 7
1481 7
1476
1482
1477 $ log 'keyword(issue)'
1483 $ log 'keyword(issue)'
1478 6
1484 6
1479 $ log 'keyword("test a")'
1485 $ log 'keyword("test a")'
1480
1486
1481 Test first (=limit) and last
1487 Test first (=limit) and last
1482
1488
1483 $ log 'limit(head(), 1)'
1489 $ log 'limit(head(), 1)'
1484 0
1490 0
1485 $ log 'limit(author("re:bob|test"), 3, 5)'
1491 $ log 'limit(author("re:bob|test"), 3, 5)'
1486 5
1492 5
1487 6
1493 6
1488 7
1494 7
1489 $ log 'limit(author("re:bob|test"), offset=6)'
1495 $ log 'limit(author("re:bob|test"), offset=6)'
1490 6
1496 6
1491 $ log 'limit(author("re:bob|test"), offset=10)'
1497 $ log 'limit(author("re:bob|test"), offset=10)'
1492 $ log 'limit(all(), 1, -1)'
1498 $ log 'limit(all(), 1, -1)'
1493 hg: parse error: negative offset
1499 hg: parse error: negative offset
1494 [255]
1500 [255]
1495 $ log 'limit(all(), -1)'
1501 $ log 'limit(all(), -1)'
1496 hg: parse error: negative number to select
1502 hg: parse error: negative number to select
1497 [255]
1503 [255]
1498 $ log 'limit(all(), 0)'
1504 $ log 'limit(all(), 0)'
1499
1505
1500 $ log 'last(all(), -1)'
1506 $ log 'last(all(), -1)'
1501 hg: parse error: negative number to select
1507 hg: parse error: negative number to select
1502 [255]
1508 [255]
1503 $ log 'last(all(), 0)'
1509 $ log 'last(all(), 0)'
1504 $ log 'last(all(), 1)'
1510 $ log 'last(all(), 1)'
1505 9
1511 9
1506 $ log 'last(all(), 2)'
1512 $ log 'last(all(), 2)'
1507 8
1513 8
1508 9
1514 9
1509
1515
1510 Test smartset.slice() by first/last()
1516 Test smartset.slice() by first/last()
1511
1517
1512 (using unoptimized set, filteredset as example)
1518 (using unoptimized set, filteredset as example)
1513
1519
1514 $ hg debugrevspec --no-show-revs -s '0:7 & branch("re:")'
1520 $ hg debugrevspec --no-show-revs -s '0:7 & branch("re:")'
1515 * set:
1521 * set:
1516 <filteredset
1522 <filteredset
1517 <spanset+ 0:8>,
1523 <spanset+ 0:8>,
1518 <branch 're:'>>
1524 <branch 're:'>>
1519 $ log 'limit(0:7 & branch("re:"), 3, 4)'
1525 $ log 'limit(0:7 & branch("re:"), 3, 4)'
1520 4
1526 4
1521 5
1527 5
1522 6
1528 6
1523 $ log 'limit(7:0 & branch("re:"), 3, 4)'
1529 $ log 'limit(7:0 & branch("re:"), 3, 4)'
1524 3
1530 3
1525 2
1531 2
1526 1
1532 1
1527 $ log 'last(0:7 & branch("re:"), 2)'
1533 $ log 'last(0:7 & branch("re:"), 2)'
1528 6
1534 6
1529 7
1535 7
1530
1536
1531 (using baseset)
1537 (using baseset)
1532
1538
1533 $ hg debugrevspec --no-show-revs -s 0+1+2+3+4+5+6+7
1539 $ hg debugrevspec --no-show-revs -s 0+1+2+3+4+5+6+7
1534 * set:
1540 * set:
1535 <baseset [0, 1, 2, 3, 4, 5, 6, 7]>
1541 <baseset [0, 1, 2, 3, 4, 5, 6, 7]>
1536 $ hg debugrevspec --no-show-revs -s 0::7
1542 $ hg debugrevspec --no-show-revs -s 0::7
1537 * set:
1543 * set:
1538 <baseset+ [0, 1, 2, 3, 4, 5, 6, 7]>
1544 <baseset+ [0, 1, 2, 3, 4, 5, 6, 7]>
1539 $ log 'limit(0+1+2+3+4+5+6+7, 3, 4)'
1545 $ log 'limit(0+1+2+3+4+5+6+7, 3, 4)'
1540 4
1546 4
1541 5
1547 5
1542 6
1548 6
1543 $ log 'limit(sort(0::7, rev), 3, 4)'
1549 $ log 'limit(sort(0::7, rev), 3, 4)'
1544 4
1550 4
1545 5
1551 5
1546 6
1552 6
1547 $ log 'limit(sort(0::7, -rev), 3, 4)'
1553 $ log 'limit(sort(0::7, -rev), 3, 4)'
1548 3
1554 3
1549 2
1555 2
1550 1
1556 1
1551 $ log 'last(sort(0::7, rev), 2)'
1557 $ log 'last(sort(0::7, rev), 2)'
1552 6
1558 6
1553 7
1559 7
1554 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 6)'
1560 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 6)'
1555 * set:
1561 * set:
1556 <baseset+ [6, 7]>
1562 <baseset+ [6, 7]>
1557 6
1563 6
1558 7
1564 7
1559 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 9)'
1565 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 9)'
1560 * set:
1566 * set:
1561 <baseset+ []>
1567 <baseset+ []>
1562 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 6)'
1568 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 6)'
1563 * set:
1569 * set:
1564 <baseset- [0, 1]>
1570 <baseset- [0, 1]>
1565 1
1571 1
1566 0
1572 0
1567 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 9)'
1573 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 9)'
1568 * set:
1574 * set:
1569 <baseset- []>
1575 <baseset- []>
1570 $ hg debugrevspec -s 'limit(0::7, 0)'
1576 $ hg debugrevspec -s 'limit(0::7, 0)'
1571 * set:
1577 * set:
1572 <baseset+ []>
1578 <baseset+ []>
1573
1579
1574 (using spanset)
1580 (using spanset)
1575
1581
1576 $ hg debugrevspec --no-show-revs -s 0:7
1582 $ hg debugrevspec --no-show-revs -s 0:7
1577 * set:
1583 * set:
1578 <spanset+ 0:8>
1584 <spanset+ 0:8>
1579 $ log 'limit(0:7, 3, 4)'
1585 $ log 'limit(0:7, 3, 4)'
1580 4
1586 4
1581 5
1587 5
1582 6
1588 6
1583 $ log 'limit(7:0, 3, 4)'
1589 $ log 'limit(7:0, 3, 4)'
1584 3
1590 3
1585 2
1591 2
1586 1
1592 1
1587 $ log 'limit(0:7, 3, 6)'
1593 $ log 'limit(0:7, 3, 6)'
1588 6
1594 6
1589 7
1595 7
1590 $ log 'limit(7:0, 3, 6)'
1596 $ log 'limit(7:0, 3, 6)'
1591 1
1597 1
1592 0
1598 0
1593 $ log 'last(0:7, 2)'
1599 $ log 'last(0:7, 2)'
1594 6
1600 6
1595 7
1601 7
1596 $ hg debugrevspec -s 'limit(0:7, 3, 6)'
1602 $ hg debugrevspec -s 'limit(0:7, 3, 6)'
1597 * set:
1603 * set:
1598 <spanset+ 6:8>
1604 <spanset+ 6:8>
1599 6
1605 6
1600 7
1606 7
1601 $ hg debugrevspec -s 'limit(0:7, 3, 9)'
1607 $ hg debugrevspec -s 'limit(0:7, 3, 9)'
1602 * set:
1608 * set:
1603 <spanset+ 8:8>
1609 <spanset+ 8:8>
1604 $ hg debugrevspec -s 'limit(7:0, 3, 6)'
1610 $ hg debugrevspec -s 'limit(7:0, 3, 6)'
1605 * set:
1611 * set:
1606 <spanset- 0:2>
1612 <spanset- 0:2>
1607 1
1613 1
1608 0
1614 0
1609 $ hg debugrevspec -s 'limit(7:0, 3, 9)'
1615 $ hg debugrevspec -s 'limit(7:0, 3, 9)'
1610 * set:
1616 * set:
1611 <spanset- 0:0>
1617 <spanset- 0:0>
1612 $ hg debugrevspec -s 'limit(0:7, 0)'
1618 $ hg debugrevspec -s 'limit(0:7, 0)'
1613 * set:
1619 * set:
1614 <spanset+ 0:0>
1620 <spanset+ 0:0>
1615
1621
1616 Test order of first/last revisions
1622 Test order of first/last revisions
1617
1623
1618 $ hg debugrevspec -s 'first(4:0, 3) & 3:'
1624 $ hg debugrevspec -s 'first(4:0, 3) & 3:'
1619 * set:
1625 * set:
1620 <filteredset
1626 <filteredset
1621 <spanset- 2:5>,
1627 <spanset- 2:5>,
1622 <spanset+ 3:10>>
1628 <spanset+ 3:10>>
1623 4
1629 4
1624 3
1630 3
1625
1631
1626 $ hg debugrevspec -s '3: & first(4:0, 3)'
1632 $ hg debugrevspec -s '3: & first(4:0, 3)'
1627 * set:
1633 * set:
1628 <filteredset
1634 <filteredset
1629 <spanset+ 3:10>,
1635 <spanset+ 3:10>,
1630 <spanset- 2:5>>
1636 <spanset- 2:5>>
1631 3
1637 3
1632 4
1638 4
1633
1639
1634 $ hg debugrevspec -s 'last(4:0, 3) & :1'
1640 $ hg debugrevspec -s 'last(4:0, 3) & :1'
1635 * set:
1641 * set:
1636 <filteredset
1642 <filteredset
1637 <spanset- 0:3>,
1643 <spanset- 0:3>,
1638 <spanset+ 0:2>>
1644 <spanset+ 0:2>>
1639 1
1645 1
1640 0
1646 0
1641
1647
1642 $ hg debugrevspec -s ':1 & last(4:0, 3)'
1648 $ hg debugrevspec -s ':1 & last(4:0, 3)'
1643 * set:
1649 * set:
1644 <filteredset
1650 <filteredset
1645 <spanset+ 0:2>,
1651 <spanset+ 0:2>,
1646 <spanset+ 0:3>>
1652 <spanset+ 0:3>>
1647 0
1653 0
1648 1
1654 1
1649
1655
1650 Test scmutil.revsingle() should return the last revision
1656 Test scmutil.revsingle() should return the last revision
1651
1657
1652 $ hg debugrevspec -s 'last(0::)'
1658 $ hg debugrevspec -s 'last(0::)'
1653 * set:
1659 * set:
1654 <baseset slice=0:1
1660 <baseset slice=0:1
1655 <generatorsetasc->>
1661 <generatorsetasc->>
1656 9
1662 9
1657 $ hg identify -r '0::' --num
1663 $ hg identify -r '0::' --num
1658 9
1664 9
1659
1665
1660 Test matching
1666 Test matching
1661
1667
1662 $ log 'matching(6)'
1668 $ log 'matching(6)'
1663 6
1669 6
1664 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
1670 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
1665 6
1671 6
1666 7
1672 7
1667
1673
1668 Testing min and max
1674 Testing min and max
1669
1675
1670 max: simple
1676 max: simple
1671
1677
1672 $ log 'max(contains(a))'
1678 $ log 'max(contains(a))'
1673 5
1679 5
1674
1680
1675 max: simple on unordered set)
1681 max: simple on unordered set)
1676
1682
1677 $ log 'max((4+0+2+5+7) and contains(a))'
1683 $ log 'max((4+0+2+5+7) and contains(a))'
1678 5
1684 5
1679
1685
1680 max: no result
1686 max: no result
1681
1687
1682 $ log 'max(contains(stringthatdoesnotappearanywhere))'
1688 $ log 'max(contains(stringthatdoesnotappearanywhere))'
1683
1689
1684 max: no result on unordered set
1690 max: no result on unordered set
1685
1691
1686 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1692 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1687
1693
1688 min: simple
1694 min: simple
1689
1695
1690 $ log 'min(contains(a))'
1696 $ log 'min(contains(a))'
1691 0
1697 0
1692
1698
1693 min: simple on unordered set
1699 min: simple on unordered set
1694
1700
1695 $ log 'min((4+0+2+5+7) and contains(a))'
1701 $ log 'min((4+0+2+5+7) and contains(a))'
1696 0
1702 0
1697
1703
1698 min: empty
1704 min: empty
1699
1705
1700 $ log 'min(contains(stringthatdoesnotappearanywhere))'
1706 $ log 'min(contains(stringthatdoesnotappearanywhere))'
1701
1707
1702 min: empty on unordered set
1708 min: empty on unordered set
1703
1709
1704 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1710 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1705
1711
1706
1712
1707 $ log 'merge()'
1713 $ log 'merge()'
1708 6
1714 6
1709 $ log 'branchpoint()'
1715 $ log 'branchpoint()'
1710 1
1716 1
1711 4
1717 4
1712 $ log 'modifies(b)'
1718 $ log 'modifies(b)'
1713 4
1719 4
1714 $ log 'modifies("path:b")'
1720 $ log 'modifies("path:b")'
1715 4
1721 4
1716 $ log 'modifies("*")'
1722 $ log 'modifies("*")'
1717 4
1723 4
1718 6
1724 6
1719 $ log 'modifies("set:modified()")'
1725 $ log 'modifies("set:modified()")'
1720 4
1726 4
1721 $ log 'id(5)'
1727 $ log 'id(5)'
1722 2
1728 2
1723 $ log 'only(9)'
1729 $ log 'only(9)'
1724 8
1730 8
1725 9
1731 9
1726 $ log 'only(8)'
1732 $ log 'only(8)'
1727 8
1733 8
1728 $ log 'only(9, 5)'
1734 $ log 'only(9, 5)'
1729 2
1735 2
1730 4
1736 4
1731 8
1737 8
1732 9
1738 9
1733 $ log 'only(7 + 9, 5 + 2)'
1739 $ log 'only(7 + 9, 5 + 2)'
1734 4
1740 4
1735 6
1741 6
1736 7
1742 7
1737 8
1743 8
1738 9
1744 9
1739
1745
1740 Test empty set input
1746 Test empty set input
1741 $ log 'only(p2())'
1747 $ log 'only(p2())'
1742 $ log 'only(p1(), p2())'
1748 $ log 'only(p1(), p2())'
1743 0
1749 0
1744 1
1750 1
1745 2
1751 2
1746 4
1752 4
1747 8
1753 8
1748 9
1754 9
1749
1755
1750 Test '%' operator
1756 Test '%' operator
1751
1757
1752 $ log '9%'
1758 $ log '9%'
1753 8
1759 8
1754 9
1760 9
1755 $ log '9%5'
1761 $ log '9%5'
1756 2
1762 2
1757 4
1763 4
1758 8
1764 8
1759 9
1765 9
1760 $ log '(7 + 9)%(5 + 2)'
1766 $ log '(7 + 9)%(5 + 2)'
1761 4
1767 4
1762 6
1768 6
1763 7
1769 7
1764 8
1770 8
1765 9
1771 9
1766
1772
1767 Test operand of '%' is optimized recursively (issue4670)
1773 Test operand of '%' is optimized recursively (issue4670)
1768
1774
1769 $ try --optimize '8:9-8%'
1775 $ try --optimize '8:9-8%'
1770 (onlypost
1776 (onlypost
1771 (minus
1777 (minus
1772 (range
1778 (range
1773 (symbol '8')
1779 (symbol '8')
1774 (symbol '9'))
1780 (symbol '9'))
1775 (symbol '8')))
1781 (symbol '8')))
1776 * optimized:
1782 * optimized:
1777 (func
1783 (func
1778 (symbol 'only')
1784 (symbol 'only')
1779 (difference
1785 (difference
1780 (range
1786 (range
1781 (symbol '8')
1787 (symbol '8')
1782 (symbol '9'))
1788 (symbol '9'))
1783 (symbol '8')))
1789 (symbol '8')))
1784 * set:
1790 * set:
1785 <baseset+ [8, 9]>
1791 <baseset+ [8, 9]>
1786 8
1792 8
1787 9
1793 9
1788 $ try --optimize '(9)%(5)'
1794 $ try --optimize '(9)%(5)'
1789 (only
1795 (only
1790 (group
1796 (group
1791 (symbol '9'))
1797 (symbol '9'))
1792 (group
1798 (group
1793 (symbol '5')))
1799 (symbol '5')))
1794 * optimized:
1800 * optimized:
1795 (func
1801 (func
1796 (symbol 'only')
1802 (symbol 'only')
1797 (list
1803 (list
1798 (symbol '9')
1804 (symbol '9')
1799 (symbol '5')))
1805 (symbol '5')))
1800 * set:
1806 * set:
1801 <baseset+ [2, 4, 8, 9]>
1807 <baseset+ [2, 4, 8, 9]>
1802 2
1808 2
1803 4
1809 4
1804 8
1810 8
1805 9
1811 9
1806
1812
1807 Test the order of operations
1813 Test the order of operations
1808
1814
1809 $ log '7 + 9%5 + 2'
1815 $ log '7 + 9%5 + 2'
1810 7
1816 7
1811 2
1817 2
1812 4
1818 4
1813 8
1819 8
1814 9
1820 9
1815
1821
1816 Test explicit numeric revision
1822 Test explicit numeric revision
1817 $ log 'rev(-2)'
1823 $ log 'rev(-2)'
1818 $ log 'rev(-1)'
1824 $ log 'rev(-1)'
1819 -1
1825 -1
1820 $ log 'rev(0)'
1826 $ log 'rev(0)'
1821 0
1827 0
1822 $ log 'rev(9)'
1828 $ log 'rev(9)'
1823 9
1829 9
1824 $ log 'rev(10)'
1830 $ log 'rev(10)'
1825 $ log 'rev(tip)'
1831 $ log 'rev(tip)'
1826 hg: parse error: rev expects a number
1832 hg: parse error: rev expects a number
1827 [255]
1833 [255]
1828
1834
1829 Test hexadecimal revision
1835 Test hexadecimal revision
1830 $ log 'id(2)'
1836 $ log 'id(2)'
1831 $ log 'id(5)'
1837 $ log 'id(5)'
1832 2
1838 2
1833 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x5)'
1839 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x5)'
1834 2
1840 2
1835 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x5'
1841 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x5'
1836 2
1842 2
1837 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x)'
1843 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x)'
1838 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x'
1844 $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x'
1839 abort: 00changelog.i@: ambiguous identifier!
1845 abort: 00changelog.i@: ambiguous identifier!
1840 [255]
1846 [255]
1841 $ log 'id(23268)'
1847 $ log 'id(23268)'
1842 4
1848 4
1843 $ log 'id(2785f51eece)'
1849 $ log 'id(2785f51eece)'
1844 0
1850 0
1845 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
1851 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
1846 8
1852 8
1847 $ log 'id(d5d0dcbdc4a)'
1853 $ log 'id(d5d0dcbdc4a)'
1848 $ log 'id(d5d0dcbdc4w)'
1854 $ log 'id(d5d0dcbdc4w)'
1849 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
1855 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
1850 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
1856 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
1851 $ log 'id(1.0)'
1857 $ log 'id(1.0)'
1852 $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
1858 $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
1853
1859
1854 Test null revision
1860 Test null revision
1855 $ log '(null)'
1861 $ log '(null)'
1856 -1
1862 -1
1857 $ log '(null:0)'
1863 $ log '(null:0)'
1858 -1
1864 -1
1859 0
1865 0
1860 $ log '(0:null)'
1866 $ log '(0:null)'
1861 0
1867 0
1862 -1
1868 -1
1863 $ log 'null::0'
1869 $ log 'null::0'
1864 -1
1870 -1
1865 0
1871 0
1866 $ log 'null:tip - 0:'
1872 $ log 'null:tip - 0:'
1867 -1
1873 -1
1868 $ log 'null: and null::' | head -1
1874 $ log 'null: and null::' | head -1
1869 -1
1875 -1
1870 $ log 'null: or 0:' | head -2
1876 $ log 'null: or 0:' | head -2
1871 -1
1877 -1
1872 0
1878 0
1873 $ log 'ancestors(null)'
1879 $ log 'ancestors(null)'
1874 -1
1880 -1
1875 $ log 'reverse(null:)' | tail -2
1881 $ log 'reverse(null:)' | tail -2
1876 0
1882 0
1877 -1
1883 -1
1878 $ log 'first(null:)'
1884 $ log 'first(null:)'
1879 -1
1885 -1
1880 $ log 'min(null:)'
1886 $ log 'min(null:)'
1881 BROKEN: should be '-1'
1887 BROKEN: should be '-1'
1882 $ log 'tip:null and all()' | tail -2
1888 $ log 'tip:null and all()' | tail -2
1883 1
1889 1
1884 0
1890 0
1885
1891
1886 Test working-directory revision
1892 Test working-directory revision
1887 $ hg debugrevspec 'wdir()'
1893 $ hg debugrevspec 'wdir()'
1888 2147483647
1894 2147483647
1889 $ hg debugrevspec 'wdir()^'
1895 $ hg debugrevspec 'wdir()^'
1890 9
1896 9
1891 $ hg up 7
1897 $ hg up 7
1892 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1898 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1893 $ hg debugrevspec 'wdir()^'
1899 $ hg debugrevspec 'wdir()^'
1894 7
1900 7
1895 $ hg debugrevspec 'wdir()^0'
1901 $ hg debugrevspec 'wdir()^0'
1896 2147483647
1902 2147483647
1897 $ hg debugrevspec 'wdir()~3'
1903 $ hg debugrevspec 'wdir()~3'
1898 5
1904 5
1899 $ hg debugrevspec 'ancestors(wdir())'
1905 $ hg debugrevspec 'ancestors(wdir())'
1900 0
1906 0
1901 1
1907 1
1902 2
1908 2
1903 3
1909 3
1904 4
1910 4
1905 5
1911 5
1906 6
1912 6
1907 7
1913 7
1908 2147483647
1914 2147483647
1909 $ hg debugrevspec '0:wdir() & ancestor(wdir())'
1915 $ hg debugrevspec '0:wdir() & ancestor(wdir())'
1910 2147483647
1916 2147483647
1911 $ hg debugrevspec '0:wdir() & ancestor(.:wdir())'
1917 $ hg debugrevspec '0:wdir() & ancestor(.:wdir())'
1912 4
1918 4
1913 $ hg debugrevspec '0:wdir() & ancestor(wdir(), wdir())'
1919 $ hg debugrevspec '0:wdir() & ancestor(wdir(), wdir())'
1914 2147483647
1920 2147483647
1915 $ hg debugrevspec '0:wdir() & ancestor(wdir(), tip)'
1921 $ hg debugrevspec '0:wdir() & ancestor(wdir(), tip)'
1916 4
1922 4
1917 $ hg debugrevspec 'null:wdir() & ancestor(wdir(), null)'
1923 $ hg debugrevspec 'null:wdir() & ancestor(wdir(), null)'
1918 -1
1924 -1
1919 $ hg debugrevspec 'wdir()~0'
1925 $ hg debugrevspec 'wdir()~0'
1920 2147483647
1926 2147483647
1921 $ hg debugrevspec 'p1(wdir())'
1927 $ hg debugrevspec 'p1(wdir())'
1922 7
1928 7
1923 $ hg debugrevspec 'p2(wdir())'
1929 $ hg debugrevspec 'p2(wdir())'
1924 $ hg debugrevspec 'parents(wdir())'
1930 $ hg debugrevspec 'parents(wdir())'
1925 7
1931 7
1926 $ hg debugrevspec 'wdir()^1'
1932 $ hg debugrevspec 'wdir()^1'
1927 7
1933 7
1928 $ hg debugrevspec 'wdir()^2'
1934 $ hg debugrevspec 'wdir()^2'
1929 $ hg debugrevspec 'wdir()^3'
1935 $ hg debugrevspec 'wdir()^3'
1930 hg: parse error: ^ expects a number 0, 1, or 2
1936 hg: parse error: ^ expects a number 0, 1, or 2
1931 [255]
1937 [255]
1932 For tests consistency
1938 For tests consistency
1933 $ hg up 9
1939 $ hg up 9
1934 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1940 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1935 $ hg debugrevspec 'tip or wdir()'
1941 $ hg debugrevspec 'tip or wdir()'
1936 9
1942 9
1937 2147483647
1943 2147483647
1938 $ hg debugrevspec '0:tip and wdir()'
1944 $ hg debugrevspec '0:tip and wdir()'
1939 $ log '0:wdir()' | tail -3
1945 $ log '0:wdir()' | tail -3
1940 8
1946 8
1941 9
1947 9
1942 2147483647
1948 2147483647
1943 $ log 'wdir():0' | head -3
1949 $ log 'wdir():0' | head -3
1944 2147483647
1950 2147483647
1945 9
1951 9
1946 8
1952 8
1947 $ log 'wdir():wdir()'
1953 $ log 'wdir():wdir()'
1948 2147483647
1954 2147483647
1949 $ log '(all() + wdir()) & min(. + wdir())'
1955 $ log '(all() + wdir()) & min(. + wdir())'
1950 9
1956 9
1951 $ log '(all() + wdir()) & max(. + wdir())'
1957 $ log '(all() + wdir()) & max(. + wdir())'
1952 2147483647
1958 2147483647
1953 $ log 'first(wdir() + .)'
1959 $ log 'first(wdir() + .)'
1954 2147483647
1960 2147483647
1955 $ log 'last(. + wdir())'
1961 $ log 'last(. + wdir())'
1956 2147483647
1962 2147483647
1957
1963
1958 Test working-directory integer revision and node id
1964 Test working-directory integer revision and node id
1959
1965
1960 $ hg debugrevspec '2147483647'
1966 $ hg debugrevspec '2147483647'
1961 2147483647
1967 2147483647
1962 $ hg debugrevspec 'rev(2147483647)'
1968 $ hg debugrevspec 'rev(2147483647)'
1963 2147483647
1969 2147483647
1964 $ hg debugrevspec 'ffffffffffffffffffffffffffffffffffffffff'
1970 $ hg debugrevspec 'ffffffffffffffffffffffffffffffffffffffff'
1965 2147483647
1971 2147483647
1966 $ hg debugrevspec 'ffffffffffff'
1972 $ hg debugrevspec 'ffffffffffff'
1967 2147483647
1973 2147483647
1968 $ hg debugrevspec 'id(ffffffffffffffffffffffffffffffffffffffff)'
1974 $ hg debugrevspec 'id(ffffffffffffffffffffffffffffffffffffffff)'
1969 2147483647
1975 2147483647
1970 $ hg debugrevspec 'id(ffffffffffff)'
1976 $ hg debugrevspec 'id(ffffffffffff)'
1971 2147483647
1977 2147483647
1972 $ hg debugrevspec 'ffffffffffff+000000000000'
1978 $ hg debugrevspec 'ffffffffffff+000000000000'
1973 2147483647
1979 2147483647
1974 -1
1980 -1
1975
1981
1976 $ cd ..
1982 $ cd ..
1977
1983
1978 Test short 'ff...' hash collision
1984 Test short 'ff...' hash collision
1979
1985
1980 $ hg init wdir-hashcollision
1986 $ hg init wdir-hashcollision
1981 $ cd wdir-hashcollision
1987 $ cd wdir-hashcollision
1982 $ cat <<EOF >> .hg/hgrc
1988 $ cat <<EOF >> .hg/hgrc
1983 > [experimental]
1989 > [experimental]
1984 > evolution.createmarkers=True
1990 > evolution.createmarkers=True
1985 > EOF
1991 > EOF
1986 $ echo 0 > a
1992 $ echo 0 > a
1987 $ hg ci -qAm 0
1993 $ hg ci -qAm 0
1988 $ for i in 2463 2961 6726 78127; do
1994 $ for i in 2463 2961 6726 78127; do
1989 > hg up -q 0
1995 > hg up -q 0
1990 > echo $i > a
1996 > echo $i > a
1991 > hg ci -qm $i
1997 > hg ci -qm $i
1992 > done
1998 > done
1993 $ hg up -q null
1999 $ hg up -q null
1994 $ hg log -r '0:wdir()' -T '{rev}:{node} {shortest(node, 3)}\n'
2000 $ hg log -r '0:wdir()' -T '{rev}:{node} {shortest(node, 3)}\n'
1995 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a b4e
2001 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a b4e
1996 1:fffbae3886c8fbb2114296380d276fd37715d571 fffba
2002 1:fffbae3886c8fbb2114296380d276fd37715d571 fffba
1997 2:fffb6093b00943f91034b9bdad069402c834e572 fffb6
2003 2:fffb6093b00943f91034b9bdad069402c834e572 fffb6
1998 3:fff48a9b9de34a4d64120c29548214c67980ade3 fff4
2004 3:fff48a9b9de34a4d64120c29548214c67980ade3 fff4
1999 4:ffff85cff0ff78504fcdc3c0bc10de0c65379249 ffff8
2005 4:ffff85cff0ff78504fcdc3c0bc10de0c65379249 ffff8
2000 2147483647:ffffffffffffffffffffffffffffffffffffffff fffff
2006 2147483647:ffffffffffffffffffffffffffffffffffffffff fffff
2001 $ hg debugobsolete fffbae3886c8fbb2114296380d276fd37715d571
2007 $ hg debugobsolete fffbae3886c8fbb2114296380d276fd37715d571
2002 obsoleted 1 changesets
2008 obsoleted 1 changesets
2003
2009
2004 $ hg debugrevspec 'fff'
2010 $ hg debugrevspec 'fff'
2005 abort: 00changelog.i@fff: ambiguous identifier!
2011 abort: 00changelog.i@fff: ambiguous identifier!
2006 [255]
2012 [255]
2007 $ hg debugrevspec 'ffff'
2013 $ hg debugrevspec 'ffff'
2008 abort: 00changelog.i@ffff: ambiguous identifier!
2014 abort: 00changelog.i@ffff: ambiguous identifier!
2009 [255]
2015 [255]
2010 $ hg debugrevspec 'fffb'
2016 $ hg debugrevspec 'fffb'
2011 abort: 00changelog.i@fffb: ambiguous identifier!
2017 abort: 00changelog.i@fffb: ambiguous identifier!
2012 [255]
2018 [255]
2013 BROKEN should be '2' (node lookup uses unfiltered repo)
2019 BROKEN should be '2' (node lookup uses unfiltered repo)
2014 $ hg debugrevspec 'id(fffb)'
2020 $ hg debugrevspec 'id(fffb)'
2015 BROKEN should be '2' (node lookup uses unfiltered repo)
2021 BROKEN should be '2' (node lookup uses unfiltered repo)
2016 $ hg debugrevspec 'ffff8'
2022 $ hg debugrevspec 'ffff8'
2017 4
2023 4
2018 $ hg debugrevspec 'fffff'
2024 $ hg debugrevspec 'fffff'
2019 2147483647
2025 2147483647
2020
2026
2021 $ cd ..
2027 $ cd ..
2022
2028
2023 Test branch() with wdir()
2029 Test branch() with wdir()
2024
2030
2025 $ cd repo
2031 $ cd repo
2026
2032
2027 $ log '0:wdir() & branch("literal:Γ©")'
2033 $ log '0:wdir() & branch("literal:Γ©")'
2028 8
2034 8
2029 9
2035 9
2030 2147483647
2036 2147483647
2031 $ log '0:wdir() & branch("re:Γ©")'
2037 $ log '0:wdir() & branch("re:Γ©")'
2032 8
2038 8
2033 9
2039 9
2034 2147483647
2040 2147483647
2035 $ log '0:wdir() & branch("re:^a")'
2041 $ log '0:wdir() & branch("re:^a")'
2036 0
2042 0
2037 2
2043 2
2038 $ log '0:wdir() & branch(8)'
2044 $ log '0:wdir() & branch(8)'
2039 8
2045 8
2040 9
2046 9
2041 2147483647
2047 2147483647
2042
2048
2043 branch(wdir()) returns all revisions belonging to the working branch. The wdir
2049 branch(wdir()) returns all revisions belonging to the working branch. The wdir
2044 itself isn't returned unless it is explicitly populated.
2050 itself isn't returned unless it is explicitly populated.
2045
2051
2046 $ log 'branch(wdir())'
2052 $ log 'branch(wdir())'
2047 8
2053 8
2048 9
2054 9
2049 $ log '0:wdir() & branch(wdir())'
2055 $ log '0:wdir() & branch(wdir())'
2050 8
2056 8
2051 9
2057 9
2052 2147483647
2058 2147483647
2053
2059
2054 $ log 'outgoing()'
2060 $ log 'outgoing()'
2055 8
2061 8
2056 9
2062 9
2057 $ log 'outgoing("../remote1")'
2063 $ log 'outgoing("../remote1")'
2058 8
2064 8
2059 9
2065 9
2060 $ log 'outgoing("../remote2")'
2066 $ log 'outgoing("../remote2")'
2061 3
2067 3
2062 5
2068 5
2063 6
2069 6
2064 7
2070 7
2065 9
2071 9
2066 $ log 'p1(merge())'
2072 $ log 'p1(merge())'
2067 5
2073 5
2068 $ log 'p2(merge())'
2074 $ log 'p2(merge())'
2069 4
2075 4
2070 $ log 'parents(merge())'
2076 $ log 'parents(merge())'
2071 4
2077 4
2072 5
2078 5
2073 $ log 'p1(branchpoint())'
2079 $ log 'p1(branchpoint())'
2074 0
2080 0
2075 2
2081 2
2076 $ log 'p2(branchpoint())'
2082 $ log 'p2(branchpoint())'
2077 $ log 'parents(branchpoint())'
2083 $ log 'parents(branchpoint())'
2078 0
2084 0
2079 2
2085 2
2080 $ log 'removes(a)'
2086 $ log 'removes(a)'
2081 2
2087 2
2082 6
2088 6
2083 $ log 'roots(all())'
2089 $ log 'roots(all())'
2084 0
2090 0
2085 $ log 'reverse(2 or 3 or 4 or 5)'
2091 $ log 'reverse(2 or 3 or 4 or 5)'
2086 5
2092 5
2087 4
2093 4
2088 3
2094 3
2089 2
2095 2
2090 $ log 'reverse(all())'
2096 $ log 'reverse(all())'
2091 9
2097 9
2092 8
2098 8
2093 7
2099 7
2094 6
2100 6
2095 5
2101 5
2096 4
2102 4
2097 3
2103 3
2098 2
2104 2
2099 1
2105 1
2100 0
2106 0
2101 $ log 'reverse(all()) & filelog(b)'
2107 $ log 'reverse(all()) & filelog(b)'
2102 4
2108 4
2103 1
2109 1
2104 $ log 'rev(5)'
2110 $ log 'rev(5)'
2105 5
2111 5
2106 $ log 'sort(limit(reverse(all()), 3))'
2112 $ log 'sort(limit(reverse(all()), 3))'
2107 7
2113 7
2108 8
2114 8
2109 9
2115 9
2110 $ log 'sort(2 or 3 or 4 or 5, date)'
2116 $ log 'sort(2 or 3 or 4 or 5, date)'
2111 2
2117 2
2112 3
2118 3
2113 5
2119 5
2114 4
2120 4
2115 $ log 'tagged()'
2121 $ log 'tagged()'
2116 6
2122 6
2117 $ log 'tag()'
2123 $ log 'tag()'
2118 6
2124 6
2119 $ log 'tag(1.0)'
2125 $ log 'tag(1.0)'
2120 6
2126 6
2121 $ log 'tag(tip)'
2127 $ log 'tag(tip)'
2122 9
2128 9
2123
2129
2124 Test order of revisions in compound expression
2130 Test order of revisions in compound expression
2125 ----------------------------------------------
2131 ----------------------------------------------
2126
2132
2127 The general rule is that only the outermost (= leftmost) predicate can
2133 The general rule is that only the outermost (= leftmost) predicate can
2128 enforce its ordering requirement. The other predicates should take the
2134 enforce its ordering requirement. The other predicates should take the
2129 ordering defined by it.
2135 ordering defined by it.
2130
2136
2131 'A & B' should follow the order of 'A':
2137 'A & B' should follow the order of 'A':
2132
2138
2133 $ log '2:0 & 0::2'
2139 $ log '2:0 & 0::2'
2134 2
2140 2
2135 1
2141 1
2136 0
2142 0
2137
2143
2138 'head()' combines sets in right order:
2144 'head()' combines sets in right order:
2139
2145
2140 $ log '2:0 & head()'
2146 $ log '2:0 & head()'
2141 2
2147 2
2142 1
2148 1
2143 0
2149 0
2144
2150
2145 'x:y' takes ordering parameter into account:
2151 'x:y' takes ordering parameter into account:
2146
2152
2147 $ try -p optimized '3:0 & 0:3 & not 2:1'
2153 $ try -p optimized '3:0 & 0:3 & not 2:1'
2148 * optimized:
2154 * optimized:
2149 (difference
2155 (difference
2150 (and
2156 (and
2151 (range
2157 (range
2152 (symbol '3')
2158 (symbol '3')
2153 (symbol '0'))
2159 (symbol '0'))
2154 (range
2160 (range
2155 (symbol '0')
2161 (symbol '0')
2156 (symbol '3')))
2162 (symbol '3')))
2157 (range
2163 (range
2158 (symbol '2')
2164 (symbol '2')
2159 (symbol '1')))
2165 (symbol '1')))
2160 * set:
2166 * set:
2161 <filteredset
2167 <filteredset
2162 <filteredset
2168 <filteredset
2163 <spanset- 0:4>,
2169 <spanset- 0:4>,
2164 <spanset+ 0:4>>,
2170 <spanset+ 0:4>>,
2165 <not
2171 <not
2166 <spanset+ 1:3>>>
2172 <spanset+ 1:3>>>
2167 3
2173 3
2168 0
2174 0
2169
2175
2170 'a + b', which is optimized to '_list(a b)', should take the ordering of
2176 'a + b', which is optimized to '_list(a b)', should take the ordering of
2171 the left expression:
2177 the left expression:
2172
2178
2173 $ try --optimize '2:0 & (0 + 1 + 2)'
2179 $ try --optimize '2:0 & (0 + 1 + 2)'
2174 (and
2180 (and
2175 (range
2181 (range
2176 (symbol '2')
2182 (symbol '2')
2177 (symbol '0'))
2183 (symbol '0'))
2178 (group
2184 (group
2179 (or
2185 (or
2180 (list
2186 (list
2181 (symbol '0')
2187 (symbol '0')
2182 (symbol '1')
2188 (symbol '1')
2183 (symbol '2')))))
2189 (symbol '2')))))
2184 * optimized:
2190 * optimized:
2185 (and
2191 (and
2186 (range
2192 (range
2187 (symbol '2')
2193 (symbol '2')
2188 (symbol '0'))
2194 (symbol '0'))
2189 (func
2195 (func
2190 (symbol '_list')
2196 (symbol '_list')
2191 (string '0\x001\x002')))
2197 (string '0\x001\x002')))
2192 * set:
2198 * set:
2193 <filteredset
2199 <filteredset
2194 <spanset- 0:3>,
2200 <spanset- 0:3>,
2195 <baseset [0, 1, 2]>>
2201 <baseset [0, 1, 2]>>
2196 2
2202 2
2197 1
2203 1
2198 0
2204 0
2199
2205
2200 'A + B' should take the ordering of the left expression:
2206 'A + B' should take the ordering of the left expression:
2201
2207
2202 $ try --optimize '2:0 & (0:1 + 2)'
2208 $ try --optimize '2:0 & (0:1 + 2)'
2203 (and
2209 (and
2204 (range
2210 (range
2205 (symbol '2')
2211 (symbol '2')
2206 (symbol '0'))
2212 (symbol '0'))
2207 (group
2213 (group
2208 (or
2214 (or
2209 (list
2215 (list
2210 (range
2216 (range
2211 (symbol '0')
2217 (symbol '0')
2212 (symbol '1'))
2218 (symbol '1'))
2213 (symbol '2')))))
2219 (symbol '2')))))
2214 * optimized:
2220 * optimized:
2215 (and
2221 (and
2216 (range
2222 (range
2217 (symbol '2')
2223 (symbol '2')
2218 (symbol '0'))
2224 (symbol '0'))
2219 (or
2225 (or
2220 (list
2226 (list
2221 (range
2227 (range
2222 (symbol '0')
2228 (symbol '0')
2223 (symbol '1'))
2229 (symbol '1'))
2224 (symbol '2'))))
2230 (symbol '2'))))
2225 * set:
2231 * set:
2226 <filteredset
2232 <filteredset
2227 <spanset- 0:3>,
2233 <spanset- 0:3>,
2228 <addset
2234 <addset
2229 <spanset+ 0:2>,
2235 <spanset+ 0:2>,
2230 <baseset [2]>>>
2236 <baseset [2]>>>
2231 2
2237 2
2232 1
2238 1
2233 0
2239 0
2234
2240
2235 '_intlist(a b)' should behave like 'a + b':
2241 '_intlist(a b)' should behave like 'a + b':
2236
2242
2237 $ trylist --optimize '2:0 & %ld' 0 1 2
2243 $ trylist --optimize '2:0 & %ld' 0 1 2
2238 (and
2244 (and
2239 (range
2245 (range
2240 (symbol '2')
2246 (symbol '2')
2241 (symbol '0'))
2247 (symbol '0'))
2242 (func
2248 (func
2243 (symbol '_intlist')
2249 (symbol '_intlist')
2244 (string '0\x001\x002')))
2250 (string '0\x001\x002')))
2245 * optimized:
2251 * optimized:
2246 (andsmally
2252 (andsmally
2247 (range
2253 (range
2248 (symbol '2')
2254 (symbol '2')
2249 (symbol '0'))
2255 (symbol '0'))
2250 (func
2256 (func
2251 (symbol '_intlist')
2257 (symbol '_intlist')
2252 (string '0\x001\x002')))
2258 (string '0\x001\x002')))
2253 * set:
2259 * set:
2254 <filteredset
2260 <filteredset
2255 <spanset- 0:3>,
2261 <spanset- 0:3>,
2256 <baseset+ [0, 1, 2]>>
2262 <baseset+ [0, 1, 2]>>
2257 2
2263 2
2258 1
2264 1
2259 0
2265 0
2260
2266
2261 $ trylist --optimize '%ld & 2:0' 0 2 1
2267 $ trylist --optimize '%ld & 2:0' 0 2 1
2262 (and
2268 (and
2263 (func
2269 (func
2264 (symbol '_intlist')
2270 (symbol '_intlist')
2265 (string '0\x002\x001'))
2271 (string '0\x002\x001'))
2266 (range
2272 (range
2267 (symbol '2')
2273 (symbol '2')
2268 (symbol '0')))
2274 (symbol '0')))
2269 * optimized:
2275 * optimized:
2270 (and
2276 (and
2271 (func
2277 (func
2272 (symbol '_intlist')
2278 (symbol '_intlist')
2273 (string '0\x002\x001'))
2279 (string '0\x002\x001'))
2274 (range
2280 (range
2275 (symbol '2')
2281 (symbol '2')
2276 (symbol '0')))
2282 (symbol '0')))
2277 * set:
2283 * set:
2278 <filteredset
2284 <filteredset
2279 <baseset [0, 2, 1]>,
2285 <baseset [0, 2, 1]>,
2280 <spanset- 0:3>>
2286 <spanset- 0:3>>
2281 0
2287 0
2282 2
2288 2
2283 1
2289 1
2284
2290
2285 '_hexlist(a b)' should behave like 'a + b':
2291 '_hexlist(a b)' should behave like 'a + b':
2286
2292
2287 $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
2293 $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
2288 (and
2294 (and
2289 (range
2295 (range
2290 (symbol '2')
2296 (symbol '2')
2291 (symbol '0'))
2297 (symbol '0'))
2292 (func
2298 (func
2293 (symbol '_hexlist')
2299 (symbol '_hexlist')
2294 (string '*'))) (glob)
2300 (string '*'))) (glob)
2295 * optimized:
2301 * optimized:
2296 (and
2302 (and
2297 (range
2303 (range
2298 (symbol '2')
2304 (symbol '2')
2299 (symbol '0'))
2305 (symbol '0'))
2300 (func
2306 (func
2301 (symbol '_hexlist')
2307 (symbol '_hexlist')
2302 (string '*'))) (glob)
2308 (string '*'))) (glob)
2303 * set:
2309 * set:
2304 <filteredset
2310 <filteredset
2305 <spanset- 0:3>,
2311 <spanset- 0:3>,
2306 <baseset [0, 1, 2]>>
2312 <baseset [0, 1, 2]>>
2307 2
2313 2
2308 1
2314 1
2309 0
2315 0
2310
2316
2311 $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
2317 $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
2312 (and
2318 (and
2313 (func
2319 (func
2314 (symbol '_hexlist')
2320 (symbol '_hexlist')
2315 (string '*')) (glob)
2321 (string '*')) (glob)
2316 (range
2322 (range
2317 (symbol '2')
2323 (symbol '2')
2318 (symbol '0')))
2324 (symbol '0')))
2319 * optimized:
2325 * optimized:
2320 (andsmally
2326 (andsmally
2321 (func
2327 (func
2322 (symbol '_hexlist')
2328 (symbol '_hexlist')
2323 (string '*')) (glob)
2329 (string '*')) (glob)
2324 (range
2330 (range
2325 (symbol '2')
2331 (symbol '2')
2326 (symbol '0')))
2332 (symbol '0')))
2327 * set:
2333 * set:
2328 <baseset [0, 2, 1]>
2334 <baseset [0, 2, 1]>
2329 0
2335 0
2330 2
2336 2
2331 1
2337 1
2332
2338
2333 '_list' should not go through the slow follow-order path if order doesn't
2339 '_list' should not go through the slow follow-order path if order doesn't
2334 matter:
2340 matter:
2335
2341
2336 $ try -p optimized '2:0 & not (0 + 1)'
2342 $ try -p optimized '2:0 & not (0 + 1)'
2337 * optimized:
2343 * optimized:
2338 (difference
2344 (difference
2339 (range
2345 (range
2340 (symbol '2')
2346 (symbol '2')
2341 (symbol '0'))
2347 (symbol '0'))
2342 (func
2348 (func
2343 (symbol '_list')
2349 (symbol '_list')
2344 (string '0\x001')))
2350 (string '0\x001')))
2345 * set:
2351 * set:
2346 <filteredset
2352 <filteredset
2347 <spanset- 0:3>,
2353 <spanset- 0:3>,
2348 <not
2354 <not
2349 <baseset [0, 1]>>>
2355 <baseset [0, 1]>>>
2350 2
2356 2
2351
2357
2352 $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
2358 $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
2353 * optimized:
2359 * optimized:
2354 (difference
2360 (difference
2355 (range
2361 (range
2356 (symbol '2')
2362 (symbol '2')
2357 (symbol '0'))
2363 (symbol '0'))
2358 (and
2364 (and
2359 (range
2365 (range
2360 (symbol '0')
2366 (symbol '0')
2361 (symbol '2'))
2367 (symbol '2'))
2362 (func
2368 (func
2363 (symbol '_list')
2369 (symbol '_list')
2364 (string '0\x001'))))
2370 (string '0\x001'))))
2365 * set:
2371 * set:
2366 <filteredset
2372 <filteredset
2367 <spanset- 0:3>,
2373 <spanset- 0:3>,
2368 <not
2374 <not
2369 <baseset [0, 1]>>>
2375 <baseset [0, 1]>>>
2370 2
2376 2
2371
2377
2372 because 'present()' does nothing other than suppressing an error, the
2378 because 'present()' does nothing other than suppressing an error, the
2373 ordering requirement should be forwarded to the nested expression
2379 ordering requirement should be forwarded to the nested expression
2374
2380
2375 $ try -p optimized 'present(2 + 0 + 1)'
2381 $ try -p optimized 'present(2 + 0 + 1)'
2376 * optimized:
2382 * optimized:
2377 (func
2383 (func
2378 (symbol 'present')
2384 (symbol 'present')
2379 (func
2385 (func
2380 (symbol '_list')
2386 (symbol '_list')
2381 (string '2\x000\x001')))
2387 (string '2\x000\x001')))
2382 * set:
2388 * set:
2383 <baseset [2, 0, 1]>
2389 <baseset [2, 0, 1]>
2384 2
2390 2
2385 0
2391 0
2386 1
2392 1
2387
2393
2388 $ try --optimize '2:0 & present(0 + 1 + 2)'
2394 $ try --optimize '2:0 & present(0 + 1 + 2)'
2389 (and
2395 (and
2390 (range
2396 (range
2391 (symbol '2')
2397 (symbol '2')
2392 (symbol '0'))
2398 (symbol '0'))
2393 (func
2399 (func
2394 (symbol 'present')
2400 (symbol 'present')
2395 (or
2401 (or
2396 (list
2402 (list
2397 (symbol '0')
2403 (symbol '0')
2398 (symbol '1')
2404 (symbol '1')
2399 (symbol '2')))))
2405 (symbol '2')))))
2400 * optimized:
2406 * optimized:
2401 (and
2407 (and
2402 (range
2408 (range
2403 (symbol '2')
2409 (symbol '2')
2404 (symbol '0'))
2410 (symbol '0'))
2405 (func
2411 (func
2406 (symbol 'present')
2412 (symbol 'present')
2407 (func
2413 (func
2408 (symbol '_list')
2414 (symbol '_list')
2409 (string '0\x001\x002'))))
2415 (string '0\x001\x002'))))
2410 * set:
2416 * set:
2411 <filteredset
2417 <filteredset
2412 <spanset- 0:3>,
2418 <spanset- 0:3>,
2413 <baseset [0, 1, 2]>>
2419 <baseset [0, 1, 2]>>
2414 2
2420 2
2415 1
2421 1
2416 0
2422 0
2417
2423
2418 'reverse()' should take effect only if it is the outermost expression:
2424 'reverse()' should take effect only if it is the outermost expression:
2419
2425
2420 $ try --optimize '0:2 & reverse(all())'
2426 $ try --optimize '0:2 & reverse(all())'
2421 (and
2427 (and
2422 (range
2428 (range
2423 (symbol '0')
2429 (symbol '0')
2424 (symbol '2'))
2430 (symbol '2'))
2425 (func
2431 (func
2426 (symbol 'reverse')
2432 (symbol 'reverse')
2427 (func
2433 (func
2428 (symbol 'all')
2434 (symbol 'all')
2429 None)))
2435 None)))
2430 * optimized:
2436 * optimized:
2431 (and
2437 (and
2432 (range
2438 (range
2433 (symbol '0')
2439 (symbol '0')
2434 (symbol '2'))
2440 (symbol '2'))
2435 (func
2441 (func
2436 (symbol 'reverse')
2442 (symbol 'reverse')
2437 (func
2443 (func
2438 (symbol 'all')
2444 (symbol 'all')
2439 None)))
2445 None)))
2440 * set:
2446 * set:
2441 <filteredset
2447 <filteredset
2442 <spanset+ 0:3>,
2448 <spanset+ 0:3>,
2443 <spanset+ 0:10>>
2449 <spanset+ 0:10>>
2444 0
2450 0
2445 1
2451 1
2446 2
2452 2
2447
2453
2448 'sort()' should take effect only if it is the outermost expression:
2454 'sort()' should take effect only if it is the outermost expression:
2449
2455
2450 $ try --optimize '0:2 & sort(all(), -rev)'
2456 $ try --optimize '0:2 & sort(all(), -rev)'
2451 (and
2457 (and
2452 (range
2458 (range
2453 (symbol '0')
2459 (symbol '0')
2454 (symbol '2'))
2460 (symbol '2'))
2455 (func
2461 (func
2456 (symbol 'sort')
2462 (symbol 'sort')
2457 (list
2463 (list
2458 (func
2464 (func
2459 (symbol 'all')
2465 (symbol 'all')
2460 None)
2466 None)
2461 (negate
2467 (negate
2462 (symbol 'rev')))))
2468 (symbol 'rev')))))
2463 * optimized:
2469 * optimized:
2464 (and
2470 (and
2465 (range
2471 (range
2466 (symbol '0')
2472 (symbol '0')
2467 (symbol '2'))
2473 (symbol '2'))
2468 (func
2474 (func
2469 (symbol 'sort')
2475 (symbol 'sort')
2470 (list
2476 (list
2471 (func
2477 (func
2472 (symbol 'all')
2478 (symbol 'all')
2473 None)
2479 None)
2474 (string '-rev'))))
2480 (string '-rev'))))
2475 * set:
2481 * set:
2476 <filteredset
2482 <filteredset
2477 <spanset+ 0:3>,
2483 <spanset+ 0:3>,
2478 <spanset+ 0:10>>
2484 <spanset+ 0:10>>
2479 0
2485 0
2480 1
2486 1
2481 2
2487 2
2482
2488
2483 invalid argument passed to noop sort():
2489 invalid argument passed to noop sort():
2484
2490
2485 $ log '0:2 & sort()'
2491 $ log '0:2 & sort()'
2486 hg: parse error: sort requires one or two arguments
2492 hg: parse error: sort requires one or two arguments
2487 [255]
2493 [255]
2488 $ log '0:2 & sort(all(), -invalid)'
2494 $ log '0:2 & sort(all(), -invalid)'
2489 hg: parse error: unknown sort key '-invalid'
2495 hg: parse error: unknown sort key '-invalid'
2490 [255]
2496 [255]
2491
2497
2492 for 'A & f(B)', 'B' should not be affected by the order of 'A':
2498 for 'A & f(B)', 'B' should not be affected by the order of 'A':
2493
2499
2494 $ try --optimize '2:0 & first(1 + 0 + 2)'
2500 $ try --optimize '2:0 & first(1 + 0 + 2)'
2495 (and
2501 (and
2496 (range
2502 (range
2497 (symbol '2')
2503 (symbol '2')
2498 (symbol '0'))
2504 (symbol '0'))
2499 (func
2505 (func
2500 (symbol 'first')
2506 (symbol 'first')
2501 (or
2507 (or
2502 (list
2508 (list
2503 (symbol '1')
2509 (symbol '1')
2504 (symbol '0')
2510 (symbol '0')
2505 (symbol '2')))))
2511 (symbol '2')))))
2506 * optimized:
2512 * optimized:
2507 (and
2513 (and
2508 (range
2514 (range
2509 (symbol '2')
2515 (symbol '2')
2510 (symbol '0'))
2516 (symbol '0'))
2511 (func
2517 (func
2512 (symbol 'first')
2518 (symbol 'first')
2513 (func
2519 (func
2514 (symbol '_list')
2520 (symbol '_list')
2515 (string '1\x000\x002'))))
2521 (string '1\x000\x002'))))
2516 * set:
2522 * set:
2517 <filteredset
2523 <filteredset
2518 <baseset [1]>,
2524 <baseset [1]>,
2519 <spanset- 0:3>>
2525 <spanset- 0:3>>
2520 1
2526 1
2521
2527
2522 $ try --optimize '2:0 & not last(0 + 2 + 1)'
2528 $ try --optimize '2:0 & not last(0 + 2 + 1)'
2523 (and
2529 (and
2524 (range
2530 (range
2525 (symbol '2')
2531 (symbol '2')
2526 (symbol '0'))
2532 (symbol '0'))
2527 (not
2533 (not
2528 (func
2534 (func
2529 (symbol 'last')
2535 (symbol 'last')
2530 (or
2536 (or
2531 (list
2537 (list
2532 (symbol '0')
2538 (symbol '0')
2533 (symbol '2')
2539 (symbol '2')
2534 (symbol '1'))))))
2540 (symbol '1'))))))
2535 * optimized:
2541 * optimized:
2536 (difference
2542 (difference
2537 (range
2543 (range
2538 (symbol '2')
2544 (symbol '2')
2539 (symbol '0'))
2545 (symbol '0'))
2540 (func
2546 (func
2541 (symbol 'last')
2547 (symbol 'last')
2542 (func
2548 (func
2543 (symbol '_list')
2549 (symbol '_list')
2544 (string '0\x002\x001'))))
2550 (string '0\x002\x001'))))
2545 * set:
2551 * set:
2546 <filteredset
2552 <filteredset
2547 <spanset- 0:3>,
2553 <spanset- 0:3>,
2548 <not
2554 <not
2549 <baseset [1]>>>
2555 <baseset [1]>>>
2550 2
2556 2
2551 0
2557 0
2552
2558
2553 for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
2559 for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
2554
2560
2555 $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
2561 $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
2556 (and
2562 (and
2557 (range
2563 (range
2558 (symbol '2')
2564 (symbol '2')
2559 (symbol '0'))
2565 (symbol '0'))
2560 (range
2566 (range
2561 (group
2567 (group
2562 (or
2568 (or
2563 (list
2569 (list
2564 (symbol '1')
2570 (symbol '1')
2565 (symbol '0')
2571 (symbol '0')
2566 (symbol '2'))))
2572 (symbol '2'))))
2567 (group
2573 (group
2568 (or
2574 (or
2569 (list
2575 (list
2570 (symbol '0')
2576 (symbol '0')
2571 (symbol '2')
2577 (symbol '2')
2572 (symbol '1'))))))
2578 (symbol '1'))))))
2573 * optimized:
2579 * optimized:
2574 (and
2580 (and
2575 (range
2581 (range
2576 (symbol '2')
2582 (symbol '2')
2577 (symbol '0'))
2583 (symbol '0'))
2578 (range
2584 (range
2579 (func
2585 (func
2580 (symbol '_list')
2586 (symbol '_list')
2581 (string '1\x000\x002'))
2587 (string '1\x000\x002'))
2582 (func
2588 (func
2583 (symbol '_list')
2589 (symbol '_list')
2584 (string '0\x002\x001'))))
2590 (string '0\x002\x001'))))
2585 * set:
2591 * set:
2586 <filteredset
2592 <filteredset
2587 <spanset- 0:3>,
2593 <spanset- 0:3>,
2588 <baseset [1]>>
2594 <baseset [1]>>
2589 1
2595 1
2590
2596
2591 'A & B' can be rewritten as 'flipand(B, A)' by weight.
2597 'A & B' can be rewritten as 'flipand(B, A)' by weight.
2592
2598
2593 $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
2599 $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
2594 (and
2600 (and
2595 (func
2601 (func
2596 (symbol 'contains')
2602 (symbol 'contains')
2597 (string 'glob:*'))
2603 (string 'glob:*'))
2598 (group
2604 (group
2599 (or
2605 (or
2600 (list
2606 (list
2601 (symbol '2')
2607 (symbol '2')
2602 (symbol '0')
2608 (symbol '0')
2603 (symbol '1')))))
2609 (symbol '1')))))
2604 * optimized:
2610 * optimized:
2605 (andsmally
2611 (andsmally
2606 (func
2612 (func
2607 (symbol 'contains')
2613 (symbol 'contains')
2608 (string 'glob:*'))
2614 (string 'glob:*'))
2609 (func
2615 (func
2610 (symbol '_list')
2616 (symbol '_list')
2611 (string '2\x000\x001')))
2617 (string '2\x000\x001')))
2612 * set:
2618 * set:
2613 <filteredset
2619 <filteredset
2614 <baseset+ [0, 1, 2]>,
2620 <baseset+ [0, 1, 2]>,
2615 <contains 'glob:*'>>
2621 <contains 'glob:*'>>
2616 0
2622 0
2617 1
2623 1
2618 2
2624 2
2619
2625
2620 and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
2626 and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
2621 the order appropriately:
2627 the order appropriately:
2622
2628
2623 $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
2629 $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
2624 (and
2630 (and
2625 (func
2631 (func
2626 (symbol 'reverse')
2632 (symbol 'reverse')
2627 (func
2633 (func
2628 (symbol 'contains')
2634 (symbol 'contains')
2629 (string 'glob:*')))
2635 (string 'glob:*')))
2630 (group
2636 (group
2631 (or
2637 (or
2632 (list
2638 (list
2633 (symbol '0')
2639 (symbol '0')
2634 (symbol '2')
2640 (symbol '2')
2635 (symbol '1')))))
2641 (symbol '1')))))
2636 * optimized:
2642 * optimized:
2637 (andsmally
2643 (andsmally
2638 (func
2644 (func
2639 (symbol 'reverse')
2645 (symbol 'reverse')
2640 (func
2646 (func
2641 (symbol 'contains')
2647 (symbol 'contains')
2642 (string 'glob:*')))
2648 (string 'glob:*')))
2643 (func
2649 (func
2644 (symbol '_list')
2650 (symbol '_list')
2645 (string '0\x002\x001')))
2651 (string '0\x002\x001')))
2646 * set:
2652 * set:
2647 <filteredset
2653 <filteredset
2648 <baseset- [0, 1, 2]>,
2654 <baseset- [0, 1, 2]>,
2649 <contains 'glob:*'>>
2655 <contains 'glob:*'>>
2650 2
2656 2
2651 1
2657 1
2652 0
2658 0
2653
2659
2654 test sort revset
2660 test sort revset
2655 --------------------------------------------
2661 --------------------------------------------
2656
2662
2657 test when adding two unordered revsets
2663 test when adding two unordered revsets
2658
2664
2659 $ log 'sort(keyword(issue) or modifies(b))'
2665 $ log 'sort(keyword(issue) or modifies(b))'
2660 4
2666 4
2661 6
2667 6
2662
2668
2663 test when sorting a reversed collection in the same way it is
2669 test when sorting a reversed collection in the same way it is
2664
2670
2665 $ log 'sort(reverse(all()), -rev)'
2671 $ log 'sort(reverse(all()), -rev)'
2666 9
2672 9
2667 8
2673 8
2668 7
2674 7
2669 6
2675 6
2670 5
2676 5
2671 4
2677 4
2672 3
2678 3
2673 2
2679 2
2674 1
2680 1
2675 0
2681 0
2676
2682
2677 test when sorting a reversed collection
2683 test when sorting a reversed collection
2678
2684
2679 $ log 'sort(reverse(all()), rev)'
2685 $ log 'sort(reverse(all()), rev)'
2680 0
2686 0
2681 1
2687 1
2682 2
2688 2
2683 3
2689 3
2684 4
2690 4
2685 5
2691 5
2686 6
2692 6
2687 7
2693 7
2688 8
2694 8
2689 9
2695 9
2690
2696
2691
2697
2692 test sorting two sorted collections in different orders
2698 test sorting two sorted collections in different orders
2693
2699
2694 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
2700 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
2695 2
2701 2
2696 6
2702 6
2697 8
2703 8
2698 9
2704 9
2699
2705
2700 test sorting two sorted collections in different orders backwards
2706 test sorting two sorted collections in different orders backwards
2701
2707
2702 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
2708 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
2703 9
2709 9
2704 8
2710 8
2705 6
2711 6
2706 2
2712 2
2707
2713
2708 test empty sort key which is noop
2714 test empty sort key which is noop
2709
2715
2710 $ log 'sort(0 + 2 + 1, "")'
2716 $ log 'sort(0 + 2 + 1, "")'
2711 0
2717 0
2712 2
2718 2
2713 1
2719 1
2714
2720
2715 test invalid sort keys
2721 test invalid sort keys
2716
2722
2717 $ log 'sort(all(), -invalid)'
2723 $ log 'sort(all(), -invalid)'
2718 hg: parse error: unknown sort key '-invalid'
2724 hg: parse error: unknown sort key '-invalid'
2719 [255]
2725 [255]
2720
2726
2721 $ cd ..
2727 $ cd ..
2722
2728
2723 test sorting by multiple keys including variable-length strings
2729 test sorting by multiple keys including variable-length strings
2724
2730
2725 $ hg init sorting
2731 $ hg init sorting
2726 $ cd sorting
2732 $ cd sorting
2727 $ cat <<EOF >> .hg/hgrc
2733 $ cat <<EOF >> .hg/hgrc
2728 > [ui]
2734 > [ui]
2729 > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
2735 > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
2730 > [templatealias]
2736 > [templatealias]
2731 > p5(s) = pad(s, 5)
2737 > p5(s) = pad(s, 5)
2732 > EOF
2738 > EOF
2733 $ hg branch -qf b12
2739 $ hg branch -qf b12
2734 $ hg ci -m m111 -u u112 -d '111 10800'
2740 $ hg ci -m m111 -u u112 -d '111 10800'
2735 $ hg branch -qf b11
2741 $ hg branch -qf b11
2736 $ hg ci -m m12 -u u111 -d '112 7200'
2742 $ hg ci -m m12 -u u111 -d '112 7200'
2737 $ hg branch -qf b111
2743 $ hg branch -qf b111
2738 $ hg ci -m m11 -u u12 -d '111 3600'
2744 $ hg ci -m m11 -u u12 -d '111 3600'
2739 $ hg branch -qf b112
2745 $ hg branch -qf b112
2740 $ hg ci -m m111 -u u11 -d '120 0'
2746 $ hg ci -m m111 -u u11 -d '120 0'
2741 $ hg branch -qf b111
2747 $ hg branch -qf b111
2742 $ hg ci -m m112 -u u111 -d '110 14400'
2748 $ hg ci -m m112 -u u111 -d '110 14400'
2743 created new head
2749 created new head
2744
2750
2745 compare revisions (has fast path):
2751 compare revisions (has fast path):
2746
2752
2747 $ hg log -r 'sort(all(), rev)'
2753 $ hg log -r 'sort(all(), rev)'
2748 0 b12 m111 u112 111 10800
2754 0 b12 m111 u112 111 10800
2749 1 b11 m12 u111 112 7200
2755 1 b11 m12 u111 112 7200
2750 2 b111 m11 u12 111 3600
2756 2 b111 m11 u12 111 3600
2751 3 b112 m111 u11 120 0
2757 3 b112 m111 u11 120 0
2752 4 b111 m112 u111 110 14400
2758 4 b111 m112 u111 110 14400
2753
2759
2754 $ hg log -r 'sort(all(), -rev)'
2760 $ hg log -r 'sort(all(), -rev)'
2755 4 b111 m112 u111 110 14400
2761 4 b111 m112 u111 110 14400
2756 3 b112 m111 u11 120 0
2762 3 b112 m111 u11 120 0
2757 2 b111 m11 u12 111 3600
2763 2 b111 m11 u12 111 3600
2758 1 b11 m12 u111 112 7200
2764 1 b11 m12 u111 112 7200
2759 0 b12 m111 u112 111 10800
2765 0 b12 m111 u112 111 10800
2760
2766
2761 compare variable-length strings (issue5218):
2767 compare variable-length strings (issue5218):
2762
2768
2763 $ hg log -r 'sort(all(), branch)'
2769 $ hg log -r 'sort(all(), branch)'
2764 1 b11 m12 u111 112 7200
2770 1 b11 m12 u111 112 7200
2765 2 b111 m11 u12 111 3600
2771 2 b111 m11 u12 111 3600
2766 4 b111 m112 u111 110 14400
2772 4 b111 m112 u111 110 14400
2767 3 b112 m111 u11 120 0
2773 3 b112 m111 u11 120 0
2768 0 b12 m111 u112 111 10800
2774 0 b12 m111 u112 111 10800
2769
2775
2770 $ hg log -r 'sort(all(), -branch)'
2776 $ hg log -r 'sort(all(), -branch)'
2771 0 b12 m111 u112 111 10800
2777 0 b12 m111 u112 111 10800
2772 3 b112 m111 u11 120 0
2778 3 b112 m111 u11 120 0
2773 2 b111 m11 u12 111 3600
2779 2 b111 m11 u12 111 3600
2774 4 b111 m112 u111 110 14400
2780 4 b111 m112 u111 110 14400
2775 1 b11 m12 u111 112 7200
2781 1 b11 m12 u111 112 7200
2776
2782
2777 $ hg log -r 'sort(all(), desc)'
2783 $ hg log -r 'sort(all(), desc)'
2778 2 b111 m11 u12 111 3600
2784 2 b111 m11 u12 111 3600
2779 0 b12 m111 u112 111 10800
2785 0 b12 m111 u112 111 10800
2780 3 b112 m111 u11 120 0
2786 3 b112 m111 u11 120 0
2781 4 b111 m112 u111 110 14400
2787 4 b111 m112 u111 110 14400
2782 1 b11 m12 u111 112 7200
2788 1 b11 m12 u111 112 7200
2783
2789
2784 $ hg log -r 'sort(all(), -desc)'
2790 $ hg log -r 'sort(all(), -desc)'
2785 1 b11 m12 u111 112 7200
2791 1 b11 m12 u111 112 7200
2786 4 b111 m112 u111 110 14400
2792 4 b111 m112 u111 110 14400
2787 0 b12 m111 u112 111 10800
2793 0 b12 m111 u112 111 10800
2788 3 b112 m111 u11 120 0
2794 3 b112 m111 u11 120 0
2789 2 b111 m11 u12 111 3600
2795 2 b111 m11 u12 111 3600
2790
2796
2791 $ hg log -r 'sort(all(), user)'
2797 $ hg log -r 'sort(all(), user)'
2792 3 b112 m111 u11 120 0
2798 3 b112 m111 u11 120 0
2793 1 b11 m12 u111 112 7200
2799 1 b11 m12 u111 112 7200
2794 4 b111 m112 u111 110 14400
2800 4 b111 m112 u111 110 14400
2795 0 b12 m111 u112 111 10800
2801 0 b12 m111 u112 111 10800
2796 2 b111 m11 u12 111 3600
2802 2 b111 m11 u12 111 3600
2797
2803
2798 $ hg log -r 'sort(all(), -user)'
2804 $ hg log -r 'sort(all(), -user)'
2799 2 b111 m11 u12 111 3600
2805 2 b111 m11 u12 111 3600
2800 0 b12 m111 u112 111 10800
2806 0 b12 m111 u112 111 10800
2801 1 b11 m12 u111 112 7200
2807 1 b11 m12 u111 112 7200
2802 4 b111 m112 u111 110 14400
2808 4 b111 m112 u111 110 14400
2803 3 b112 m111 u11 120 0
2809 3 b112 m111 u11 120 0
2804
2810
2805 compare dates (tz offset should have no effect):
2811 compare dates (tz offset should have no effect):
2806
2812
2807 $ hg log -r 'sort(all(), date)'
2813 $ hg log -r 'sort(all(), date)'
2808 4 b111 m112 u111 110 14400
2814 4 b111 m112 u111 110 14400
2809 0 b12 m111 u112 111 10800
2815 0 b12 m111 u112 111 10800
2810 2 b111 m11 u12 111 3600
2816 2 b111 m11 u12 111 3600
2811 1 b11 m12 u111 112 7200
2817 1 b11 m12 u111 112 7200
2812 3 b112 m111 u11 120 0
2818 3 b112 m111 u11 120 0
2813
2819
2814 $ hg log -r 'sort(all(), -date)'
2820 $ hg log -r 'sort(all(), -date)'
2815 3 b112 m111 u11 120 0
2821 3 b112 m111 u11 120 0
2816 1 b11 m12 u111 112 7200
2822 1 b11 m12 u111 112 7200
2817 0 b12 m111 u112 111 10800
2823 0 b12 m111 u112 111 10800
2818 2 b111 m11 u12 111 3600
2824 2 b111 m11 u12 111 3600
2819 4 b111 m112 u111 110 14400
2825 4 b111 m112 u111 110 14400
2820
2826
2821 be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
2827 be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
2822 because '-k' reverses the comparison, not the list itself:
2828 because '-k' reverses the comparison, not the list itself:
2823
2829
2824 $ hg log -r 'sort(0 + 2, date)'
2830 $ hg log -r 'sort(0 + 2, date)'
2825 0 b12 m111 u112 111 10800
2831 0 b12 m111 u112 111 10800
2826 2 b111 m11 u12 111 3600
2832 2 b111 m11 u12 111 3600
2827
2833
2828 $ hg log -r 'sort(0 + 2, -date)'
2834 $ hg log -r 'sort(0 + 2, -date)'
2829 0 b12 m111 u112 111 10800
2835 0 b12 m111 u112 111 10800
2830 2 b111 m11 u12 111 3600
2836 2 b111 m11 u12 111 3600
2831
2837
2832 $ hg log -r 'reverse(sort(0 + 2, date))'
2838 $ hg log -r 'reverse(sort(0 + 2, date))'
2833 2 b111 m11 u12 111 3600
2839 2 b111 m11 u12 111 3600
2834 0 b12 m111 u112 111 10800
2840 0 b12 m111 u112 111 10800
2835
2841
2836 sort by multiple keys:
2842 sort by multiple keys:
2837
2843
2838 $ hg log -r 'sort(all(), "branch -rev")'
2844 $ hg log -r 'sort(all(), "branch -rev")'
2839 1 b11 m12 u111 112 7200
2845 1 b11 m12 u111 112 7200
2840 4 b111 m112 u111 110 14400
2846 4 b111 m112 u111 110 14400
2841 2 b111 m11 u12 111 3600
2847 2 b111 m11 u12 111 3600
2842 3 b112 m111 u11 120 0
2848 3 b112 m111 u11 120 0
2843 0 b12 m111 u112 111 10800
2849 0 b12 m111 u112 111 10800
2844
2850
2845 $ hg log -r 'sort(all(), "-desc -date")'
2851 $ hg log -r 'sort(all(), "-desc -date")'
2846 1 b11 m12 u111 112 7200
2852 1 b11 m12 u111 112 7200
2847 4 b111 m112 u111 110 14400
2853 4 b111 m112 u111 110 14400
2848 3 b112 m111 u11 120 0
2854 3 b112 m111 u11 120 0
2849 0 b12 m111 u112 111 10800
2855 0 b12 m111 u112 111 10800
2850 2 b111 m11 u12 111 3600
2856 2 b111 m11 u12 111 3600
2851
2857
2852 $ hg log -r 'sort(all(), "user -branch date rev")'
2858 $ hg log -r 'sort(all(), "user -branch date rev")'
2853 3 b112 m111 u11 120 0
2859 3 b112 m111 u11 120 0
2854 4 b111 m112 u111 110 14400
2860 4 b111 m112 u111 110 14400
2855 1 b11 m12 u111 112 7200
2861 1 b11 m12 u111 112 7200
2856 0 b12 m111 u112 111 10800
2862 0 b12 m111 u112 111 10800
2857 2 b111 m11 u12 111 3600
2863 2 b111 m11 u12 111 3600
2858
2864
2859 toposort prioritises graph branches
2865 toposort prioritises graph branches
2860
2866
2861 $ hg up 2
2867 $ hg up 2
2862 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
2868 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
2863 $ touch a
2869 $ touch a
2864 $ hg addremove
2870 $ hg addremove
2865 adding a
2871 adding a
2866 $ hg ci -m 't1' -u 'tu' -d '130 0'
2872 $ hg ci -m 't1' -u 'tu' -d '130 0'
2867 created new head
2873 created new head
2868 $ echo 'a' >> a
2874 $ echo 'a' >> a
2869 $ hg ci -m 't2' -u 'tu' -d '130 0'
2875 $ hg ci -m 't2' -u 'tu' -d '130 0'
2870 $ hg book book1
2876 $ hg book book1
2871 $ hg up 4
2877 $ hg up 4
2872 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2878 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2873 (leaving bookmark book1)
2879 (leaving bookmark book1)
2874 $ touch a
2880 $ touch a
2875 $ hg addremove
2881 $ hg addremove
2876 adding a
2882 adding a
2877 $ hg ci -m 't3' -u 'tu' -d '130 0'
2883 $ hg ci -m 't3' -u 'tu' -d '130 0'
2878
2884
2879 $ hg log -r 'sort(all(), topo)'
2885 $ hg log -r 'sort(all(), topo)'
2880 7 b111 t3 tu 130 0
2886 7 b111 t3 tu 130 0
2881 4 b111 m112 u111 110 14400
2887 4 b111 m112 u111 110 14400
2882 3 b112 m111 u11 120 0
2888 3 b112 m111 u11 120 0
2883 6 b111 t2 tu 130 0
2889 6 b111 t2 tu 130 0
2884 5 b111 t1 tu 130 0
2890 5 b111 t1 tu 130 0
2885 2 b111 m11 u12 111 3600
2891 2 b111 m11 u12 111 3600
2886 1 b11 m12 u111 112 7200
2892 1 b11 m12 u111 112 7200
2887 0 b12 m111 u112 111 10800
2893 0 b12 m111 u112 111 10800
2888
2894
2889 $ hg log -r 'sort(all(), -topo)'
2895 $ hg log -r 'sort(all(), -topo)'
2890 0 b12 m111 u112 111 10800
2896 0 b12 m111 u112 111 10800
2891 1 b11 m12 u111 112 7200
2897 1 b11 m12 u111 112 7200
2892 2 b111 m11 u12 111 3600
2898 2 b111 m11 u12 111 3600
2893 5 b111 t1 tu 130 0
2899 5 b111 t1 tu 130 0
2894 6 b111 t2 tu 130 0
2900 6 b111 t2 tu 130 0
2895 3 b112 m111 u11 120 0
2901 3 b112 m111 u11 120 0
2896 4 b111 m112 u111 110 14400
2902 4 b111 m112 u111 110 14400
2897 7 b111 t3 tu 130 0
2903 7 b111 t3 tu 130 0
2898
2904
2899 $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
2905 $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
2900 6 b111 t2 tu 130 0
2906 6 b111 t2 tu 130 0
2901 5 b111 t1 tu 130 0
2907 5 b111 t1 tu 130 0
2902 7 b111 t3 tu 130 0
2908 7 b111 t3 tu 130 0
2903 4 b111 m112 u111 110 14400
2909 4 b111 m112 u111 110 14400
2904 3 b112 m111 u11 120 0
2910 3 b112 m111 u11 120 0
2905 2 b111 m11 u12 111 3600
2911 2 b111 m11 u12 111 3600
2906 1 b11 m12 u111 112 7200
2912 1 b11 m12 u111 112 7200
2907 0 b12 m111 u112 111 10800
2913 0 b12 m111 u112 111 10800
2908
2914
2909 topographical sorting can't be combined with other sort keys, and you can't
2915 topographical sorting can't be combined with other sort keys, and you can't
2910 use the topo.firstbranch option when topo sort is not active:
2916 use the topo.firstbranch option when topo sort is not active:
2911
2917
2912 $ hg log -r 'sort(all(), "topo user")'
2918 $ hg log -r 'sort(all(), "topo user")'
2913 hg: parse error: topo sort order cannot be combined with other sort keys
2919 hg: parse error: topo sort order cannot be combined with other sort keys
2914 [255]
2920 [255]
2915
2921
2916 $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
2922 $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
2917 hg: parse error: topo.firstbranch can only be used when using the topo sort key
2923 hg: parse error: topo.firstbranch can only be used when using the topo sort key
2918 [255]
2924 [255]
2919
2925
2920 topo.firstbranch should accept any kind of expressions:
2926 topo.firstbranch should accept any kind of expressions:
2921
2927
2922 $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
2928 $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
2923 0 b12 m111 u112 111 10800
2929 0 b12 m111 u112 111 10800
2924
2930
2925 $ cd ..
2931 $ cd ..
2926 $ cd repo
2932 $ cd repo
2927
2933
2928 test multiline revset with errors
2934 test multiline revset with errors
2929
2935
2930 $ echo > multiline-revset
2936 $ echo > multiline-revset
2931 $ echo '. +' >> multiline-revset
2937 $ echo '. +' >> multiline-revset
2932 $ echo '.^ +' >> multiline-revset
2938 $ echo '.^ +' >> multiline-revset
2933 $ hg log -r "`cat multiline-revset`"
2939 $ hg log -r "`cat multiline-revset`"
2934 hg: parse error at 9: not a prefix: end
2940 hg: parse error at 9: not a prefix: end
2935 ( . + .^ +
2941 ( . + .^ +
2936 ^ here)
2942 ^ here)
2937 [255]
2943 [255]
2938 $ hg debugrevspec -v 'revset(first(rev(0)))' -p all
2944 $ hg debugrevspec -v 'revset(first(rev(0)))' -p all
2939 * parsed:
2945 * parsed:
2940 (func
2946 (func
2941 (symbol 'revset')
2947 (symbol 'revset')
2942 (func
2948 (func
2943 (symbol 'first')
2949 (symbol 'first')
2944 (func
2950 (func
2945 (symbol 'rev')
2951 (symbol 'rev')
2946 (symbol '0'))))
2952 (symbol '0'))))
2947 * expanded:
2953 * expanded:
2948 (func
2954 (func
2949 (symbol 'revset')
2955 (symbol 'revset')
2950 (func
2956 (func
2951 (symbol 'first')
2957 (symbol 'first')
2952 (func
2958 (func
2953 (symbol 'rev')
2959 (symbol 'rev')
2954 (symbol '0'))))
2960 (symbol '0'))))
2955 * concatenated:
2961 * concatenated:
2956 (func
2962 (func
2957 (symbol 'revset')
2963 (symbol 'revset')
2958 (func
2964 (func
2959 (symbol 'first')
2965 (symbol 'first')
2960 (func
2966 (func
2961 (symbol 'rev')
2967 (symbol 'rev')
2962 (symbol '0'))))
2968 (symbol '0'))))
2963 * analyzed:
2969 * analyzed:
2964 (func
2970 (func
2965 (symbol 'revset')
2971 (symbol 'revset')
2966 (func
2972 (func
2967 (symbol 'first')
2973 (symbol 'first')
2968 (func
2974 (func
2969 (symbol 'rev')
2975 (symbol 'rev')
2970 (symbol '0'))))
2976 (symbol '0'))))
2971 * optimized:
2977 * optimized:
2972 (func
2978 (func
2973 (symbol 'revset')
2979 (symbol 'revset')
2974 (func
2980 (func
2975 (symbol 'first')
2981 (symbol 'first')
2976 (func
2982 (func
2977 (symbol 'rev')
2983 (symbol 'rev')
2978 (symbol '0'))))
2984 (symbol '0'))))
2979 * set:
2985 * set:
2980 <baseset+ [0]>
2986 <baseset+ [0]>
2981 0
2987 0
2982
2988
2983 abort if the revset doesn't expect given size
2989 abort if the revset doesn't expect given size
2984 $ log 'expectsize()'
2990 $ log 'expectsize()'
2985 hg: parse error: invalid set of arguments
2991 hg: parse error: invalid set of arguments
2986 [255]
2992 [255]
2987 $ log 'expectsize(0:2, a)'
2993 $ log 'expectsize(0:2, a)'
2988 hg: parse error: expectsize requires a size range or a positive integer
2994 hg: parse error: expectsize requires a size range or a positive integer
2989 [255]
2995 [255]
2990 $ log 'expectsize(0:2, 3)'
2996 $ log 'expectsize(0:2, 3)'
2991 0
2997 0
2992 1
2998 1
2993 2
2999 2
2994
3000
2995 $ log 'expectsize(2:0, 3)'
3001 $ log 'expectsize(2:0, 3)'
2996 2
3002 2
2997 1
3003 1
2998 0
3004 0
2999 $ log 'expectsize(0:1, 1)'
3005 $ log 'expectsize(0:1, 1)'
3000 abort: revset size mismatch. expected 1, got 2!
3006 abort: revset size mismatch. expected 1, got 2!
3001 [255]
3007 [255]
3002 $ log 'expectsize(0:4, -1)'
3008 $ log 'expectsize(0:4, -1)'
3003 hg: parse error: negative size
3009 hg: parse error: negative size
3004 [255]
3010 [255]
3005 $ log 'expectsize(0:2, 2:4)'
3011 $ log 'expectsize(0:2, 2:4)'
3006 0
3012 0
3007 1
3013 1
3008 2
3014 2
3009 $ log 'expectsize(0:1, 3:5)'
3015 $ log 'expectsize(0:1, 3:5)'
3010 abort: revset size mismatch. expected between 3 and 5, got 2!
3016 abort: revset size mismatch. expected between 3 and 5, got 2!
3011 [255]
3017 [255]
3012 $ log 'expectsize(0:1, -1:2)'
3018 $ log 'expectsize(0:1, -1:2)'
3013 hg: parse error: negative size
3019 hg: parse error: negative size
3014 [255]
3020 [255]
3015 $ log 'expectsize(0:1, 1:-2)'
3021 $ log 'expectsize(0:1, 1:-2)'
3016 hg: parse error: negative size
3022 hg: parse error: negative size
3017 [255]
3023 [255]
3018 $ log 'expectsize(0:2, a:4)'
3024 $ log 'expectsize(0:2, a:4)'
3019 hg: parse error: size range bounds must be integers
3025 hg: parse error: size range bounds must be integers
3020 [255]
3026 [255]
3021 $ log 'expectsize(0:2, 2:b)'
3027 $ log 'expectsize(0:2, 2:b)'
3022 hg: parse error: size range bounds must be integers
3028 hg: parse error: size range bounds must be integers
3023 [255]
3029 [255]
3024 $ log 'expectsize(0:2, 2:)'
3030 $ log 'expectsize(0:2, 2:)'
3025 0
3031 0
3026 1
3032 1
3027 2
3033 2
3028 $ log 'expectsize(0:2, :5)'
3034 $ log 'expectsize(0:2, :5)'
3029 0
3035 0
3030 1
3036 1
3031 2
3037 2
3032 $ log 'expectsize(0:2, :)'
3038 $ log 'expectsize(0:2, :)'
3033 0
3039 0
3034 1
3040 1
3035 2
3041 2
3036 $ log 'expectsize(0:2, 4:)'
3042 $ log 'expectsize(0:2, 4:)'
3037 abort: revset size mismatch. expected between 4 and 11, got 3!
3043 abort: revset size mismatch. expected between 4 and 11, got 3!
3038 [255]
3044 [255]
3039 $ log 'expectsize(0:2, :2)'
3045 $ log 'expectsize(0:2, :2)'
3040 abort: revset size mismatch. expected between 0 and 2, got 3!
3046 abort: revset size mismatch. expected between 0 and 2, got 3!
3041 [255]
3047 [255]
General Comments 0
You need to be logged in to leave comments. Login now