##// END OF EJS Templates
extensions: show deprecation warning for the use of cmdutil.command...
Yuya Nishihara -
r32343:d47d7d3b default
parent child Browse files
Show More
@@ -1,3445 +1,3448 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullid,
20 nullid,
21 nullrev,
21 nullrev,
22 short,
22 short,
23 )
23 )
24
24
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 changelog,
27 changelog,
28 copies,
28 copies,
29 crecord as crecordmod,
29 crecord as crecordmod,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 lock as lockmod,
34 lock as lockmod,
35 match as matchmod,
35 match as matchmod,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 pycompat,
40 pycompat,
41 registrar,
41 registrar,
42 repair,
42 repair,
43 revlog,
43 revlog,
44 revset,
44 revset,
45 scmutil,
45 scmutil,
46 smartset,
46 smartset,
47 templatekw,
47 templatekw,
48 templater,
48 templater,
49 util,
49 util,
50 vfs as vfsmod,
50 vfs as vfsmod,
51 )
51 )
52 stringio = util.stringio
52 stringio = util.stringio
53
53
54 # special string such that everything below this line will be ingored in the
54 # special string such that everything below this line will be ingored in the
55 # editor text
55 # editor text
56 _linebelow = "^HG: ------------------------ >8 ------------------------$"
56 _linebelow = "^HG: ------------------------ >8 ------------------------$"
57
57
58 def ishunk(x):
58 def ishunk(x):
59 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
59 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
60 return isinstance(x, hunkclasses)
60 return isinstance(x, hunkclasses)
61
61
62 def newandmodified(chunks, originalchunks):
62 def newandmodified(chunks, originalchunks):
63 newlyaddedandmodifiedfiles = set()
63 newlyaddedandmodifiedfiles = set()
64 for chunk in chunks:
64 for chunk in chunks:
65 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
65 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
66 originalchunks:
66 originalchunks:
67 newlyaddedandmodifiedfiles.add(chunk.header.filename())
67 newlyaddedandmodifiedfiles.add(chunk.header.filename())
68 return newlyaddedandmodifiedfiles
68 return newlyaddedandmodifiedfiles
69
69
70 def parsealiases(cmd):
70 def parsealiases(cmd):
71 return cmd.lstrip("^").split("|")
71 return cmd.lstrip("^").split("|")
72
72
73 def setupwrapcolorwrite(ui):
73 def setupwrapcolorwrite(ui):
74 # wrap ui.write so diff output can be labeled/colorized
74 # wrap ui.write so diff output can be labeled/colorized
75 def wrapwrite(orig, *args, **kw):
75 def wrapwrite(orig, *args, **kw):
76 label = kw.pop('label', '')
76 label = kw.pop('label', '')
77 for chunk, l in patch.difflabel(lambda: args):
77 for chunk, l in patch.difflabel(lambda: args):
78 orig(chunk, label=label + l)
78 orig(chunk, label=label + l)
79
79
80 oldwrite = ui.write
80 oldwrite = ui.write
81 def wrap(*args, **kwargs):
81 def wrap(*args, **kwargs):
82 return wrapwrite(oldwrite, *args, **kwargs)
82 return wrapwrite(oldwrite, *args, **kwargs)
83 setattr(ui, 'write', wrap)
83 setattr(ui, 'write', wrap)
84 return oldwrite
84 return oldwrite
85
85
86 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
86 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
87 if usecurses:
87 if usecurses:
88 if testfile:
88 if testfile:
89 recordfn = crecordmod.testdecorator(testfile,
89 recordfn = crecordmod.testdecorator(testfile,
90 crecordmod.testchunkselector)
90 crecordmod.testchunkselector)
91 else:
91 else:
92 recordfn = crecordmod.chunkselector
92 recordfn = crecordmod.chunkselector
93
93
94 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
94 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
95
95
96 else:
96 else:
97 return patch.filterpatch(ui, originalhunks, operation)
97 return patch.filterpatch(ui, originalhunks, operation)
98
98
99 def recordfilter(ui, originalhunks, operation=None):
99 def recordfilter(ui, originalhunks, operation=None):
100 """ Prompts the user to filter the originalhunks and return a list of
100 """ Prompts the user to filter the originalhunks and return a list of
101 selected hunks.
101 selected hunks.
102 *operation* is used for to build ui messages to indicate the user what
102 *operation* is used for to build ui messages to indicate the user what
103 kind of filtering they are doing: reverting, committing, shelving, etc.
103 kind of filtering they are doing: reverting, committing, shelving, etc.
104 (see patch.filterpatch).
104 (see patch.filterpatch).
105 """
105 """
106 usecurses = crecordmod.checkcurses(ui)
106 usecurses = crecordmod.checkcurses(ui)
107 testfile = ui.config('experimental', 'crecordtest', None)
107 testfile = ui.config('experimental', 'crecordtest', None)
108 oldwrite = setupwrapcolorwrite(ui)
108 oldwrite = setupwrapcolorwrite(ui)
109 try:
109 try:
110 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
110 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
111 testfile, operation)
111 testfile, operation)
112 finally:
112 finally:
113 ui.write = oldwrite
113 ui.write = oldwrite
114 return newchunks, newopts
114 return newchunks, newopts
115
115
116 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
116 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
117 filterfn, *pats, **opts):
117 filterfn, *pats, **opts):
118 from . import merge as mergemod
118 from . import merge as mergemod
119 opts = pycompat.byteskwargs(opts)
119 opts = pycompat.byteskwargs(opts)
120 if not ui.interactive():
120 if not ui.interactive():
121 if cmdsuggest:
121 if cmdsuggest:
122 msg = _('running non-interactively, use %s instead') % cmdsuggest
122 msg = _('running non-interactively, use %s instead') % cmdsuggest
123 else:
123 else:
124 msg = _('running non-interactively')
124 msg = _('running non-interactively')
125 raise error.Abort(msg)
125 raise error.Abort(msg)
126
126
127 # make sure username is set before going interactive
127 # make sure username is set before going interactive
128 if not opts.get('user'):
128 if not opts.get('user'):
129 ui.username() # raise exception, username not provided
129 ui.username() # raise exception, username not provided
130
130
131 def recordfunc(ui, repo, message, match, opts):
131 def recordfunc(ui, repo, message, match, opts):
132 """This is generic record driver.
132 """This is generic record driver.
133
133
134 Its job is to interactively filter local changes, and
134 Its job is to interactively filter local changes, and
135 accordingly prepare working directory into a state in which the
135 accordingly prepare working directory into a state in which the
136 job can be delegated to a non-interactive commit command such as
136 job can be delegated to a non-interactive commit command such as
137 'commit' or 'qrefresh'.
137 'commit' or 'qrefresh'.
138
138
139 After the actual job is done by non-interactive command, the
139 After the actual job is done by non-interactive command, the
140 working directory is restored to its original state.
140 working directory is restored to its original state.
141
141
142 In the end we'll record interesting changes, and everything else
142 In the end we'll record interesting changes, and everything else
143 will be left in place, so the user can continue working.
143 will be left in place, so the user can continue working.
144 """
144 """
145
145
146 checkunfinished(repo, commit=True)
146 checkunfinished(repo, commit=True)
147 wctx = repo[None]
147 wctx = repo[None]
148 merge = len(wctx.parents()) > 1
148 merge = len(wctx.parents()) > 1
149 if merge:
149 if merge:
150 raise error.Abort(_('cannot partially commit a merge '
150 raise error.Abort(_('cannot partially commit a merge '
151 '(use "hg commit" instead)'))
151 '(use "hg commit" instead)'))
152
152
153 def fail(f, msg):
153 def fail(f, msg):
154 raise error.Abort('%s: %s' % (f, msg))
154 raise error.Abort('%s: %s' % (f, msg))
155
155
156 force = opts.get('force')
156 force = opts.get('force')
157 if not force:
157 if not force:
158 vdirs = []
158 vdirs = []
159 match.explicitdir = vdirs.append
159 match.explicitdir = vdirs.append
160 match.bad = fail
160 match.bad = fail
161
161
162 status = repo.status(match=match)
162 status = repo.status(match=match)
163 if not force:
163 if not force:
164 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
164 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
165 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
165 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
166 diffopts.nodates = True
166 diffopts.nodates = True
167 diffopts.git = True
167 diffopts.git = True
168 diffopts.showfunc = True
168 diffopts.showfunc = True
169 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
169 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
170 originalchunks = patch.parsepatch(originaldiff)
170 originalchunks = patch.parsepatch(originaldiff)
171
171
172 # 1. filter patch, since we are intending to apply subset of it
172 # 1. filter patch, since we are intending to apply subset of it
173 try:
173 try:
174 chunks, newopts = filterfn(ui, originalchunks)
174 chunks, newopts = filterfn(ui, originalchunks)
175 except patch.PatchError as err:
175 except patch.PatchError as err:
176 raise error.Abort(_('error parsing patch: %s') % err)
176 raise error.Abort(_('error parsing patch: %s') % err)
177 opts.update(newopts)
177 opts.update(newopts)
178
178
179 # We need to keep a backup of files that have been newly added and
179 # We need to keep a backup of files that have been newly added and
180 # modified during the recording process because there is a previous
180 # modified during the recording process because there is a previous
181 # version without the edit in the workdir
181 # version without the edit in the workdir
182 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
182 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
183 contenders = set()
183 contenders = set()
184 for h in chunks:
184 for h in chunks:
185 try:
185 try:
186 contenders.update(set(h.files()))
186 contenders.update(set(h.files()))
187 except AttributeError:
187 except AttributeError:
188 pass
188 pass
189
189
190 changed = status.modified + status.added + status.removed
190 changed = status.modified + status.added + status.removed
191 newfiles = [f for f in changed if f in contenders]
191 newfiles = [f for f in changed if f in contenders]
192 if not newfiles:
192 if not newfiles:
193 ui.status(_('no changes to record\n'))
193 ui.status(_('no changes to record\n'))
194 return 0
194 return 0
195
195
196 modified = set(status.modified)
196 modified = set(status.modified)
197
197
198 # 2. backup changed files, so we can restore them in the end
198 # 2. backup changed files, so we can restore them in the end
199
199
200 if backupall:
200 if backupall:
201 tobackup = changed
201 tobackup = changed
202 else:
202 else:
203 tobackup = [f for f in newfiles if f in modified or f in \
203 tobackup = [f for f in newfiles if f in modified or f in \
204 newlyaddedandmodifiedfiles]
204 newlyaddedandmodifiedfiles]
205 backups = {}
205 backups = {}
206 if tobackup:
206 if tobackup:
207 backupdir = repo.vfs.join('record-backups')
207 backupdir = repo.vfs.join('record-backups')
208 try:
208 try:
209 os.mkdir(backupdir)
209 os.mkdir(backupdir)
210 except OSError as err:
210 except OSError as err:
211 if err.errno != errno.EEXIST:
211 if err.errno != errno.EEXIST:
212 raise
212 raise
213 try:
213 try:
214 # backup continues
214 # backup continues
215 for f in tobackup:
215 for f in tobackup:
216 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
216 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
217 dir=backupdir)
217 dir=backupdir)
218 os.close(fd)
218 os.close(fd)
219 ui.debug('backup %r as %r\n' % (f, tmpname))
219 ui.debug('backup %r as %r\n' % (f, tmpname))
220 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
220 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
221 backups[f] = tmpname
221 backups[f] = tmpname
222
222
223 fp = stringio()
223 fp = stringio()
224 for c in chunks:
224 for c in chunks:
225 fname = c.filename()
225 fname = c.filename()
226 if fname in backups:
226 if fname in backups:
227 c.write(fp)
227 c.write(fp)
228 dopatch = fp.tell()
228 dopatch = fp.tell()
229 fp.seek(0)
229 fp.seek(0)
230
230
231 # 2.5 optionally review / modify patch in text editor
231 # 2.5 optionally review / modify patch in text editor
232 if opts.get('review', False):
232 if opts.get('review', False):
233 patchtext = (crecordmod.diffhelptext
233 patchtext = (crecordmod.diffhelptext
234 + crecordmod.patchhelptext
234 + crecordmod.patchhelptext
235 + fp.read())
235 + fp.read())
236 reviewedpatch = ui.edit(patchtext, "",
236 reviewedpatch = ui.edit(patchtext, "",
237 extra={"suffix": ".diff"},
237 extra={"suffix": ".diff"},
238 repopath=repo.path)
238 repopath=repo.path)
239 fp.truncate(0)
239 fp.truncate(0)
240 fp.write(reviewedpatch)
240 fp.write(reviewedpatch)
241 fp.seek(0)
241 fp.seek(0)
242
242
243 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
243 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
244 # 3a. apply filtered patch to clean repo (clean)
244 # 3a. apply filtered patch to clean repo (clean)
245 if backups:
245 if backups:
246 # Equivalent to hg.revert
246 # Equivalent to hg.revert
247 m = scmutil.matchfiles(repo, backups.keys())
247 m = scmutil.matchfiles(repo, backups.keys())
248 mergemod.update(repo, repo.dirstate.p1(),
248 mergemod.update(repo, repo.dirstate.p1(),
249 False, True, matcher=m)
249 False, True, matcher=m)
250
250
251 # 3b. (apply)
251 # 3b. (apply)
252 if dopatch:
252 if dopatch:
253 try:
253 try:
254 ui.debug('applying patch\n')
254 ui.debug('applying patch\n')
255 ui.debug(fp.getvalue())
255 ui.debug(fp.getvalue())
256 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
256 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
257 except patch.PatchError as err:
257 except patch.PatchError as err:
258 raise error.Abort(str(err))
258 raise error.Abort(str(err))
259 del fp
259 del fp
260
260
261 # 4. We prepared working directory according to filtered
261 # 4. We prepared working directory according to filtered
262 # patch. Now is the time to delegate the job to
262 # patch. Now is the time to delegate the job to
263 # commit/qrefresh or the like!
263 # commit/qrefresh or the like!
264
264
265 # Make all of the pathnames absolute.
265 # Make all of the pathnames absolute.
266 newfiles = [repo.wjoin(nf) for nf in newfiles]
266 newfiles = [repo.wjoin(nf) for nf in newfiles]
267 return commitfunc(ui, repo, *newfiles, **opts)
267 return commitfunc(ui, repo, *newfiles, **opts)
268 finally:
268 finally:
269 # 5. finally restore backed-up files
269 # 5. finally restore backed-up files
270 try:
270 try:
271 dirstate = repo.dirstate
271 dirstate = repo.dirstate
272 for realname, tmpname in backups.iteritems():
272 for realname, tmpname in backups.iteritems():
273 ui.debug('restoring %r to %r\n' % (tmpname, realname))
273 ui.debug('restoring %r to %r\n' % (tmpname, realname))
274
274
275 if dirstate[realname] == 'n':
275 if dirstate[realname] == 'n':
276 # without normallookup, restoring timestamp
276 # without normallookup, restoring timestamp
277 # may cause partially committed files
277 # may cause partially committed files
278 # to be treated as unmodified
278 # to be treated as unmodified
279 dirstate.normallookup(realname)
279 dirstate.normallookup(realname)
280
280
281 # copystat=True here and above are a hack to trick any
281 # copystat=True here and above are a hack to trick any
282 # editors that have f open that we haven't modified them.
282 # editors that have f open that we haven't modified them.
283 #
283 #
284 # Also note that this racy as an editor could notice the
284 # Also note that this racy as an editor could notice the
285 # file's mtime before we've finished writing it.
285 # file's mtime before we've finished writing it.
286 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
286 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
287 os.unlink(tmpname)
287 os.unlink(tmpname)
288 if tobackup:
288 if tobackup:
289 os.rmdir(backupdir)
289 os.rmdir(backupdir)
290 except OSError:
290 except OSError:
291 pass
291 pass
292
292
293 def recordinwlock(ui, repo, message, match, opts):
293 def recordinwlock(ui, repo, message, match, opts):
294 with repo.wlock():
294 with repo.wlock():
295 return recordfunc(ui, repo, message, match, opts)
295 return recordfunc(ui, repo, message, match, opts)
296
296
297 return commit(ui, repo, recordinwlock, pats, opts)
297 return commit(ui, repo, recordinwlock, pats, opts)
298
298
299 def findpossible(cmd, table, strict=False):
299 def findpossible(cmd, table, strict=False):
300 """
300 """
301 Return cmd -> (aliases, command table entry)
301 Return cmd -> (aliases, command table entry)
302 for each matching command.
302 for each matching command.
303 Return debug commands (or their aliases) only if no normal command matches.
303 Return debug commands (or their aliases) only if no normal command matches.
304 """
304 """
305 choice = {}
305 choice = {}
306 debugchoice = {}
306 debugchoice = {}
307
307
308 if cmd in table:
308 if cmd in table:
309 # short-circuit exact matches, "log" alias beats "^log|history"
309 # short-circuit exact matches, "log" alias beats "^log|history"
310 keys = [cmd]
310 keys = [cmd]
311 else:
311 else:
312 keys = table.keys()
312 keys = table.keys()
313
313
314 allcmds = []
314 allcmds = []
315 for e in keys:
315 for e in keys:
316 aliases = parsealiases(e)
316 aliases = parsealiases(e)
317 allcmds.extend(aliases)
317 allcmds.extend(aliases)
318 found = None
318 found = None
319 if cmd in aliases:
319 if cmd in aliases:
320 found = cmd
320 found = cmd
321 elif not strict:
321 elif not strict:
322 for a in aliases:
322 for a in aliases:
323 if a.startswith(cmd):
323 if a.startswith(cmd):
324 found = a
324 found = a
325 break
325 break
326 if found is not None:
326 if found is not None:
327 if aliases[0].startswith("debug") or found.startswith("debug"):
327 if aliases[0].startswith("debug") or found.startswith("debug"):
328 debugchoice[found] = (aliases, table[e])
328 debugchoice[found] = (aliases, table[e])
329 else:
329 else:
330 choice[found] = (aliases, table[e])
330 choice[found] = (aliases, table[e])
331
331
332 if not choice and debugchoice:
332 if not choice and debugchoice:
333 choice = debugchoice
333 choice = debugchoice
334
334
335 return choice, allcmds
335 return choice, allcmds
336
336
337 def findcmd(cmd, table, strict=True):
337 def findcmd(cmd, table, strict=True):
338 """Return (aliases, command table entry) for command string."""
338 """Return (aliases, command table entry) for command string."""
339 choice, allcmds = findpossible(cmd, table, strict)
339 choice, allcmds = findpossible(cmd, table, strict)
340
340
341 if cmd in choice:
341 if cmd in choice:
342 return choice[cmd]
342 return choice[cmd]
343
343
344 if len(choice) > 1:
344 if len(choice) > 1:
345 clist = choice.keys()
345 clist = choice.keys()
346 clist.sort()
346 clist.sort()
347 raise error.AmbiguousCommand(cmd, clist)
347 raise error.AmbiguousCommand(cmd, clist)
348
348
349 if choice:
349 if choice:
350 return choice.values()[0]
350 return choice.values()[0]
351
351
352 raise error.UnknownCommand(cmd, allcmds)
352 raise error.UnknownCommand(cmd, allcmds)
353
353
354 def findrepo(p):
354 def findrepo(p):
355 while not os.path.isdir(os.path.join(p, ".hg")):
355 while not os.path.isdir(os.path.join(p, ".hg")):
356 oldp, p = p, os.path.dirname(p)
356 oldp, p = p, os.path.dirname(p)
357 if p == oldp:
357 if p == oldp:
358 return None
358 return None
359
359
360 return p
360 return p
361
361
362 def bailifchanged(repo, merge=True, hint=None):
362 def bailifchanged(repo, merge=True, hint=None):
363 """ enforce the precondition that working directory must be clean.
363 """ enforce the precondition that working directory must be clean.
364
364
365 'merge' can be set to false if a pending uncommitted merge should be
365 'merge' can be set to false if a pending uncommitted merge should be
366 ignored (such as when 'update --check' runs).
366 ignored (such as when 'update --check' runs).
367
367
368 'hint' is the usual hint given to Abort exception.
368 'hint' is the usual hint given to Abort exception.
369 """
369 """
370
370
371 if merge and repo.dirstate.p2() != nullid:
371 if merge and repo.dirstate.p2() != nullid:
372 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
372 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
373 modified, added, removed, deleted = repo.status()[:4]
373 modified, added, removed, deleted = repo.status()[:4]
374 if modified or added or removed or deleted:
374 if modified or added or removed or deleted:
375 raise error.Abort(_('uncommitted changes'), hint=hint)
375 raise error.Abort(_('uncommitted changes'), hint=hint)
376 ctx = repo[None]
376 ctx = repo[None]
377 for s in sorted(ctx.substate):
377 for s in sorted(ctx.substate):
378 ctx.sub(s).bailifchanged(hint=hint)
378 ctx.sub(s).bailifchanged(hint=hint)
379
379
380 def logmessage(ui, opts):
380 def logmessage(ui, opts):
381 """ get the log message according to -m and -l option """
381 """ get the log message according to -m and -l option """
382 message = opts.get('message')
382 message = opts.get('message')
383 logfile = opts.get('logfile')
383 logfile = opts.get('logfile')
384
384
385 if message and logfile:
385 if message and logfile:
386 raise error.Abort(_('options --message and --logfile are mutually '
386 raise error.Abort(_('options --message and --logfile are mutually '
387 'exclusive'))
387 'exclusive'))
388 if not message and logfile:
388 if not message and logfile:
389 try:
389 try:
390 if logfile == '-':
390 if logfile == '-':
391 message = ui.fin.read()
391 message = ui.fin.read()
392 else:
392 else:
393 message = '\n'.join(util.readfile(logfile).splitlines())
393 message = '\n'.join(util.readfile(logfile).splitlines())
394 except IOError as inst:
394 except IOError as inst:
395 raise error.Abort(_("can't read commit message '%s': %s") %
395 raise error.Abort(_("can't read commit message '%s': %s") %
396 (logfile, inst.strerror))
396 (logfile, inst.strerror))
397 return message
397 return message
398
398
399 def mergeeditform(ctxorbool, baseformname):
399 def mergeeditform(ctxorbool, baseformname):
400 """return appropriate editform name (referencing a committemplate)
400 """return appropriate editform name (referencing a committemplate)
401
401
402 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
402 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
403 merging is committed.
403 merging is committed.
404
404
405 This returns baseformname with '.merge' appended if it is a merge,
405 This returns baseformname with '.merge' appended if it is a merge,
406 otherwise '.normal' is appended.
406 otherwise '.normal' is appended.
407 """
407 """
408 if isinstance(ctxorbool, bool):
408 if isinstance(ctxorbool, bool):
409 if ctxorbool:
409 if ctxorbool:
410 return baseformname + ".merge"
410 return baseformname + ".merge"
411 elif 1 < len(ctxorbool.parents()):
411 elif 1 < len(ctxorbool.parents()):
412 return baseformname + ".merge"
412 return baseformname + ".merge"
413
413
414 return baseformname + ".normal"
414 return baseformname + ".normal"
415
415
416 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
416 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
417 editform='', **opts):
417 editform='', **opts):
418 """get appropriate commit message editor according to '--edit' option
418 """get appropriate commit message editor according to '--edit' option
419
419
420 'finishdesc' is a function to be called with edited commit message
420 'finishdesc' is a function to be called with edited commit message
421 (= 'description' of the new changeset) just after editing, but
421 (= 'description' of the new changeset) just after editing, but
422 before checking empty-ness. It should return actual text to be
422 before checking empty-ness. It should return actual text to be
423 stored into history. This allows to change description before
423 stored into history. This allows to change description before
424 storing.
424 storing.
425
425
426 'extramsg' is a extra message to be shown in the editor instead of
426 'extramsg' is a extra message to be shown in the editor instead of
427 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
427 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
428 is automatically added.
428 is automatically added.
429
429
430 'editform' is a dot-separated list of names, to distinguish
430 'editform' is a dot-separated list of names, to distinguish
431 the purpose of commit text editing.
431 the purpose of commit text editing.
432
432
433 'getcommiteditor' returns 'commitforceeditor' regardless of
433 'getcommiteditor' returns 'commitforceeditor' regardless of
434 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
434 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
435 they are specific for usage in MQ.
435 they are specific for usage in MQ.
436 """
436 """
437 if edit or finishdesc or extramsg:
437 if edit or finishdesc or extramsg:
438 return lambda r, c, s: commitforceeditor(r, c, s,
438 return lambda r, c, s: commitforceeditor(r, c, s,
439 finishdesc=finishdesc,
439 finishdesc=finishdesc,
440 extramsg=extramsg,
440 extramsg=extramsg,
441 editform=editform)
441 editform=editform)
442 elif editform:
442 elif editform:
443 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
443 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
444 else:
444 else:
445 return commiteditor
445 return commiteditor
446
446
447 def loglimit(opts):
447 def loglimit(opts):
448 """get the log limit according to option -l/--limit"""
448 """get the log limit according to option -l/--limit"""
449 limit = opts.get('limit')
449 limit = opts.get('limit')
450 if limit:
450 if limit:
451 try:
451 try:
452 limit = int(limit)
452 limit = int(limit)
453 except ValueError:
453 except ValueError:
454 raise error.Abort(_('limit must be a positive integer'))
454 raise error.Abort(_('limit must be a positive integer'))
455 if limit <= 0:
455 if limit <= 0:
456 raise error.Abort(_('limit must be positive'))
456 raise error.Abort(_('limit must be positive'))
457 else:
457 else:
458 limit = None
458 limit = None
459 return limit
459 return limit
460
460
461 def makefilename(repo, pat, node, desc=None,
461 def makefilename(repo, pat, node, desc=None,
462 total=None, seqno=None, revwidth=None, pathname=None):
462 total=None, seqno=None, revwidth=None, pathname=None):
463 node_expander = {
463 node_expander = {
464 'H': lambda: hex(node),
464 'H': lambda: hex(node),
465 'R': lambda: str(repo.changelog.rev(node)),
465 'R': lambda: str(repo.changelog.rev(node)),
466 'h': lambda: short(node),
466 'h': lambda: short(node),
467 'm': lambda: re.sub('[^\w]', '_', str(desc))
467 'm': lambda: re.sub('[^\w]', '_', str(desc))
468 }
468 }
469 expander = {
469 expander = {
470 '%': lambda: '%',
470 '%': lambda: '%',
471 'b': lambda: os.path.basename(repo.root),
471 'b': lambda: os.path.basename(repo.root),
472 }
472 }
473
473
474 try:
474 try:
475 if node:
475 if node:
476 expander.update(node_expander)
476 expander.update(node_expander)
477 if node:
477 if node:
478 expander['r'] = (lambda:
478 expander['r'] = (lambda:
479 str(repo.changelog.rev(node)).zfill(revwidth or 0))
479 str(repo.changelog.rev(node)).zfill(revwidth or 0))
480 if total is not None:
480 if total is not None:
481 expander['N'] = lambda: str(total)
481 expander['N'] = lambda: str(total)
482 if seqno is not None:
482 if seqno is not None:
483 expander['n'] = lambda: str(seqno)
483 expander['n'] = lambda: str(seqno)
484 if total is not None and seqno is not None:
484 if total is not None and seqno is not None:
485 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
485 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
486 if pathname is not None:
486 if pathname is not None:
487 expander['s'] = lambda: os.path.basename(pathname)
487 expander['s'] = lambda: os.path.basename(pathname)
488 expander['d'] = lambda: os.path.dirname(pathname) or '.'
488 expander['d'] = lambda: os.path.dirname(pathname) or '.'
489 expander['p'] = lambda: pathname
489 expander['p'] = lambda: pathname
490
490
491 newname = []
491 newname = []
492 patlen = len(pat)
492 patlen = len(pat)
493 i = 0
493 i = 0
494 while i < patlen:
494 while i < patlen:
495 c = pat[i:i + 1]
495 c = pat[i:i + 1]
496 if c == '%':
496 if c == '%':
497 i += 1
497 i += 1
498 c = pat[i:i + 1]
498 c = pat[i:i + 1]
499 c = expander[c]()
499 c = expander[c]()
500 newname.append(c)
500 newname.append(c)
501 i += 1
501 i += 1
502 return ''.join(newname)
502 return ''.join(newname)
503 except KeyError as inst:
503 except KeyError as inst:
504 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
504 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
505 inst.args[0])
505 inst.args[0])
506
506
507 class _unclosablefile(object):
507 class _unclosablefile(object):
508 def __init__(self, fp):
508 def __init__(self, fp):
509 self._fp = fp
509 self._fp = fp
510
510
511 def close(self):
511 def close(self):
512 pass
512 pass
513
513
514 def __iter__(self):
514 def __iter__(self):
515 return iter(self._fp)
515 return iter(self._fp)
516
516
517 def __getattr__(self, attr):
517 def __getattr__(self, attr):
518 return getattr(self._fp, attr)
518 return getattr(self._fp, attr)
519
519
520 def __enter__(self):
520 def __enter__(self):
521 return self
521 return self
522
522
523 def __exit__(self, exc_type, exc_value, exc_tb):
523 def __exit__(self, exc_type, exc_value, exc_tb):
524 pass
524 pass
525
525
526 def makefileobj(repo, pat, node=None, desc=None, total=None,
526 def makefileobj(repo, pat, node=None, desc=None, total=None,
527 seqno=None, revwidth=None, mode='wb', modemap=None,
527 seqno=None, revwidth=None, mode='wb', modemap=None,
528 pathname=None):
528 pathname=None):
529
529
530 writable = mode not in ('r', 'rb')
530 writable = mode not in ('r', 'rb')
531
531
532 if not pat or pat == '-':
532 if not pat or pat == '-':
533 if writable:
533 if writable:
534 fp = repo.ui.fout
534 fp = repo.ui.fout
535 else:
535 else:
536 fp = repo.ui.fin
536 fp = repo.ui.fin
537 return _unclosablefile(fp)
537 return _unclosablefile(fp)
538 if util.safehasattr(pat, 'write') and writable:
538 if util.safehasattr(pat, 'write') and writable:
539 return pat
539 return pat
540 if util.safehasattr(pat, 'read') and 'r' in mode:
540 if util.safehasattr(pat, 'read') and 'r' in mode:
541 return pat
541 return pat
542 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
542 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
543 if modemap is not None:
543 if modemap is not None:
544 mode = modemap.get(fn, mode)
544 mode = modemap.get(fn, mode)
545 if mode == 'wb':
545 if mode == 'wb':
546 modemap[fn] = 'ab'
546 modemap[fn] = 'ab'
547 return open(fn, mode)
547 return open(fn, mode)
548
548
549 def openrevlog(repo, cmd, file_, opts):
549 def openrevlog(repo, cmd, file_, opts):
550 """opens the changelog, manifest, a filelog or a given revlog"""
550 """opens the changelog, manifest, a filelog or a given revlog"""
551 cl = opts['changelog']
551 cl = opts['changelog']
552 mf = opts['manifest']
552 mf = opts['manifest']
553 dir = opts['dir']
553 dir = opts['dir']
554 msg = None
554 msg = None
555 if cl and mf:
555 if cl and mf:
556 msg = _('cannot specify --changelog and --manifest at the same time')
556 msg = _('cannot specify --changelog and --manifest at the same time')
557 elif cl and dir:
557 elif cl and dir:
558 msg = _('cannot specify --changelog and --dir at the same time')
558 msg = _('cannot specify --changelog and --dir at the same time')
559 elif cl or mf or dir:
559 elif cl or mf or dir:
560 if file_:
560 if file_:
561 msg = _('cannot specify filename with --changelog or --manifest')
561 msg = _('cannot specify filename with --changelog or --manifest')
562 elif not repo:
562 elif not repo:
563 msg = _('cannot specify --changelog or --manifest or --dir '
563 msg = _('cannot specify --changelog or --manifest or --dir '
564 'without a repository')
564 'without a repository')
565 if msg:
565 if msg:
566 raise error.Abort(msg)
566 raise error.Abort(msg)
567
567
568 r = None
568 r = None
569 if repo:
569 if repo:
570 if cl:
570 if cl:
571 r = repo.unfiltered().changelog
571 r = repo.unfiltered().changelog
572 elif dir:
572 elif dir:
573 if 'treemanifest' not in repo.requirements:
573 if 'treemanifest' not in repo.requirements:
574 raise error.Abort(_("--dir can only be used on repos with "
574 raise error.Abort(_("--dir can only be used on repos with "
575 "treemanifest enabled"))
575 "treemanifest enabled"))
576 dirlog = repo.manifestlog._revlog.dirlog(dir)
576 dirlog = repo.manifestlog._revlog.dirlog(dir)
577 if len(dirlog):
577 if len(dirlog):
578 r = dirlog
578 r = dirlog
579 elif mf:
579 elif mf:
580 r = repo.manifestlog._revlog
580 r = repo.manifestlog._revlog
581 elif file_:
581 elif file_:
582 filelog = repo.file(file_)
582 filelog = repo.file(file_)
583 if len(filelog):
583 if len(filelog):
584 r = filelog
584 r = filelog
585 if not r:
585 if not r:
586 if not file_:
586 if not file_:
587 raise error.CommandError(cmd, _('invalid arguments'))
587 raise error.CommandError(cmd, _('invalid arguments'))
588 if not os.path.isfile(file_):
588 if not os.path.isfile(file_):
589 raise error.Abort(_("revlog '%s' not found") % file_)
589 raise error.Abort(_("revlog '%s' not found") % file_)
590 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
590 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
591 file_[:-2] + ".i")
591 file_[:-2] + ".i")
592 return r
592 return r
593
593
594 def copy(ui, repo, pats, opts, rename=False):
594 def copy(ui, repo, pats, opts, rename=False):
595 # called with the repo lock held
595 # called with the repo lock held
596 #
596 #
597 # hgsep => pathname that uses "/" to separate directories
597 # hgsep => pathname that uses "/" to separate directories
598 # ossep => pathname that uses os.sep to separate directories
598 # ossep => pathname that uses os.sep to separate directories
599 cwd = repo.getcwd()
599 cwd = repo.getcwd()
600 targets = {}
600 targets = {}
601 after = opts.get("after")
601 after = opts.get("after")
602 dryrun = opts.get("dry_run")
602 dryrun = opts.get("dry_run")
603 wctx = repo[None]
603 wctx = repo[None]
604
604
605 def walkpat(pat):
605 def walkpat(pat):
606 srcs = []
606 srcs = []
607 if after:
607 if after:
608 badstates = '?'
608 badstates = '?'
609 else:
609 else:
610 badstates = '?r'
610 badstates = '?r'
611 m = scmutil.match(repo[None], [pat], opts, globbed=True)
611 m = scmutil.match(repo[None], [pat], opts, globbed=True)
612 for abs in repo.walk(m):
612 for abs in repo.walk(m):
613 state = repo.dirstate[abs]
613 state = repo.dirstate[abs]
614 rel = m.rel(abs)
614 rel = m.rel(abs)
615 exact = m.exact(abs)
615 exact = m.exact(abs)
616 if state in badstates:
616 if state in badstates:
617 if exact and state == '?':
617 if exact and state == '?':
618 ui.warn(_('%s: not copying - file is not managed\n') % rel)
618 ui.warn(_('%s: not copying - file is not managed\n') % rel)
619 if exact and state == 'r':
619 if exact and state == 'r':
620 ui.warn(_('%s: not copying - file has been marked for'
620 ui.warn(_('%s: not copying - file has been marked for'
621 ' remove\n') % rel)
621 ' remove\n') % rel)
622 continue
622 continue
623 # abs: hgsep
623 # abs: hgsep
624 # rel: ossep
624 # rel: ossep
625 srcs.append((abs, rel, exact))
625 srcs.append((abs, rel, exact))
626 return srcs
626 return srcs
627
627
628 # abssrc: hgsep
628 # abssrc: hgsep
629 # relsrc: ossep
629 # relsrc: ossep
630 # otarget: ossep
630 # otarget: ossep
631 def copyfile(abssrc, relsrc, otarget, exact):
631 def copyfile(abssrc, relsrc, otarget, exact):
632 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
632 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
633 if '/' in abstarget:
633 if '/' in abstarget:
634 # We cannot normalize abstarget itself, this would prevent
634 # We cannot normalize abstarget itself, this would prevent
635 # case only renames, like a => A.
635 # case only renames, like a => A.
636 abspath, absname = abstarget.rsplit('/', 1)
636 abspath, absname = abstarget.rsplit('/', 1)
637 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
637 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
638 reltarget = repo.pathto(abstarget, cwd)
638 reltarget = repo.pathto(abstarget, cwd)
639 target = repo.wjoin(abstarget)
639 target = repo.wjoin(abstarget)
640 src = repo.wjoin(abssrc)
640 src = repo.wjoin(abssrc)
641 state = repo.dirstate[abstarget]
641 state = repo.dirstate[abstarget]
642
642
643 scmutil.checkportable(ui, abstarget)
643 scmutil.checkportable(ui, abstarget)
644
644
645 # check for collisions
645 # check for collisions
646 prevsrc = targets.get(abstarget)
646 prevsrc = targets.get(abstarget)
647 if prevsrc is not None:
647 if prevsrc is not None:
648 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
648 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
649 (reltarget, repo.pathto(abssrc, cwd),
649 (reltarget, repo.pathto(abssrc, cwd),
650 repo.pathto(prevsrc, cwd)))
650 repo.pathto(prevsrc, cwd)))
651 return
651 return
652
652
653 # check for overwrites
653 # check for overwrites
654 exists = os.path.lexists(target)
654 exists = os.path.lexists(target)
655 samefile = False
655 samefile = False
656 if exists and abssrc != abstarget:
656 if exists and abssrc != abstarget:
657 if (repo.dirstate.normalize(abssrc) ==
657 if (repo.dirstate.normalize(abssrc) ==
658 repo.dirstate.normalize(abstarget)):
658 repo.dirstate.normalize(abstarget)):
659 if not rename:
659 if not rename:
660 ui.warn(_("%s: can't copy - same file\n") % reltarget)
660 ui.warn(_("%s: can't copy - same file\n") % reltarget)
661 return
661 return
662 exists = False
662 exists = False
663 samefile = True
663 samefile = True
664
664
665 if not after and exists or after and state in 'mn':
665 if not after and exists or after and state in 'mn':
666 if not opts['force']:
666 if not opts['force']:
667 if state in 'mn':
667 if state in 'mn':
668 msg = _('%s: not overwriting - file already committed\n')
668 msg = _('%s: not overwriting - file already committed\n')
669 if after:
669 if after:
670 flags = '--after --force'
670 flags = '--after --force'
671 else:
671 else:
672 flags = '--force'
672 flags = '--force'
673 if rename:
673 if rename:
674 hint = _('(hg rename %s to replace the file by '
674 hint = _('(hg rename %s to replace the file by '
675 'recording a rename)\n') % flags
675 'recording a rename)\n') % flags
676 else:
676 else:
677 hint = _('(hg copy %s to replace the file by '
677 hint = _('(hg copy %s to replace the file by '
678 'recording a copy)\n') % flags
678 'recording a copy)\n') % flags
679 else:
679 else:
680 msg = _('%s: not overwriting - file exists\n')
680 msg = _('%s: not overwriting - file exists\n')
681 if rename:
681 if rename:
682 hint = _('(hg rename --after to record the rename)\n')
682 hint = _('(hg rename --after to record the rename)\n')
683 else:
683 else:
684 hint = _('(hg copy --after to record the copy)\n')
684 hint = _('(hg copy --after to record the copy)\n')
685 ui.warn(msg % reltarget)
685 ui.warn(msg % reltarget)
686 ui.warn(hint)
686 ui.warn(hint)
687 return
687 return
688
688
689 if after:
689 if after:
690 if not exists:
690 if not exists:
691 if rename:
691 if rename:
692 ui.warn(_('%s: not recording move - %s does not exist\n') %
692 ui.warn(_('%s: not recording move - %s does not exist\n') %
693 (relsrc, reltarget))
693 (relsrc, reltarget))
694 else:
694 else:
695 ui.warn(_('%s: not recording copy - %s does not exist\n') %
695 ui.warn(_('%s: not recording copy - %s does not exist\n') %
696 (relsrc, reltarget))
696 (relsrc, reltarget))
697 return
697 return
698 elif not dryrun:
698 elif not dryrun:
699 try:
699 try:
700 if exists:
700 if exists:
701 os.unlink(target)
701 os.unlink(target)
702 targetdir = os.path.dirname(target) or '.'
702 targetdir = os.path.dirname(target) or '.'
703 if not os.path.isdir(targetdir):
703 if not os.path.isdir(targetdir):
704 os.makedirs(targetdir)
704 os.makedirs(targetdir)
705 if samefile:
705 if samefile:
706 tmp = target + "~hgrename"
706 tmp = target + "~hgrename"
707 os.rename(src, tmp)
707 os.rename(src, tmp)
708 os.rename(tmp, target)
708 os.rename(tmp, target)
709 else:
709 else:
710 util.copyfile(src, target)
710 util.copyfile(src, target)
711 srcexists = True
711 srcexists = True
712 except IOError as inst:
712 except IOError as inst:
713 if inst.errno == errno.ENOENT:
713 if inst.errno == errno.ENOENT:
714 ui.warn(_('%s: deleted in working directory\n') % relsrc)
714 ui.warn(_('%s: deleted in working directory\n') % relsrc)
715 srcexists = False
715 srcexists = False
716 else:
716 else:
717 ui.warn(_('%s: cannot copy - %s\n') %
717 ui.warn(_('%s: cannot copy - %s\n') %
718 (relsrc, inst.strerror))
718 (relsrc, inst.strerror))
719 return True # report a failure
719 return True # report a failure
720
720
721 if ui.verbose or not exact:
721 if ui.verbose or not exact:
722 if rename:
722 if rename:
723 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
723 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
724 else:
724 else:
725 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
725 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
726
726
727 targets[abstarget] = abssrc
727 targets[abstarget] = abssrc
728
728
729 # fix up dirstate
729 # fix up dirstate
730 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
730 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
731 dryrun=dryrun, cwd=cwd)
731 dryrun=dryrun, cwd=cwd)
732 if rename and not dryrun:
732 if rename and not dryrun:
733 if not after and srcexists and not samefile:
733 if not after and srcexists and not samefile:
734 repo.wvfs.unlinkpath(abssrc)
734 repo.wvfs.unlinkpath(abssrc)
735 wctx.forget([abssrc])
735 wctx.forget([abssrc])
736
736
737 # pat: ossep
737 # pat: ossep
738 # dest ossep
738 # dest ossep
739 # srcs: list of (hgsep, hgsep, ossep, bool)
739 # srcs: list of (hgsep, hgsep, ossep, bool)
740 # return: function that takes hgsep and returns ossep
740 # return: function that takes hgsep and returns ossep
741 def targetpathfn(pat, dest, srcs):
741 def targetpathfn(pat, dest, srcs):
742 if os.path.isdir(pat):
742 if os.path.isdir(pat):
743 abspfx = pathutil.canonpath(repo.root, cwd, pat)
743 abspfx = pathutil.canonpath(repo.root, cwd, pat)
744 abspfx = util.localpath(abspfx)
744 abspfx = util.localpath(abspfx)
745 if destdirexists:
745 if destdirexists:
746 striplen = len(os.path.split(abspfx)[0])
746 striplen = len(os.path.split(abspfx)[0])
747 else:
747 else:
748 striplen = len(abspfx)
748 striplen = len(abspfx)
749 if striplen:
749 if striplen:
750 striplen += len(pycompat.ossep)
750 striplen += len(pycompat.ossep)
751 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
751 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
752 elif destdirexists:
752 elif destdirexists:
753 res = lambda p: os.path.join(dest,
753 res = lambda p: os.path.join(dest,
754 os.path.basename(util.localpath(p)))
754 os.path.basename(util.localpath(p)))
755 else:
755 else:
756 res = lambda p: dest
756 res = lambda p: dest
757 return res
757 return res
758
758
759 # pat: ossep
759 # pat: ossep
760 # dest ossep
760 # dest ossep
761 # srcs: list of (hgsep, hgsep, ossep, bool)
761 # srcs: list of (hgsep, hgsep, ossep, bool)
762 # return: function that takes hgsep and returns ossep
762 # return: function that takes hgsep and returns ossep
763 def targetpathafterfn(pat, dest, srcs):
763 def targetpathafterfn(pat, dest, srcs):
764 if matchmod.patkind(pat):
764 if matchmod.patkind(pat):
765 # a mercurial pattern
765 # a mercurial pattern
766 res = lambda p: os.path.join(dest,
766 res = lambda p: os.path.join(dest,
767 os.path.basename(util.localpath(p)))
767 os.path.basename(util.localpath(p)))
768 else:
768 else:
769 abspfx = pathutil.canonpath(repo.root, cwd, pat)
769 abspfx = pathutil.canonpath(repo.root, cwd, pat)
770 if len(abspfx) < len(srcs[0][0]):
770 if len(abspfx) < len(srcs[0][0]):
771 # A directory. Either the target path contains the last
771 # A directory. Either the target path contains the last
772 # component of the source path or it does not.
772 # component of the source path or it does not.
773 def evalpath(striplen):
773 def evalpath(striplen):
774 score = 0
774 score = 0
775 for s in srcs:
775 for s in srcs:
776 t = os.path.join(dest, util.localpath(s[0])[striplen:])
776 t = os.path.join(dest, util.localpath(s[0])[striplen:])
777 if os.path.lexists(t):
777 if os.path.lexists(t):
778 score += 1
778 score += 1
779 return score
779 return score
780
780
781 abspfx = util.localpath(abspfx)
781 abspfx = util.localpath(abspfx)
782 striplen = len(abspfx)
782 striplen = len(abspfx)
783 if striplen:
783 if striplen:
784 striplen += len(pycompat.ossep)
784 striplen += len(pycompat.ossep)
785 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
785 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
786 score = evalpath(striplen)
786 score = evalpath(striplen)
787 striplen1 = len(os.path.split(abspfx)[0])
787 striplen1 = len(os.path.split(abspfx)[0])
788 if striplen1:
788 if striplen1:
789 striplen1 += len(pycompat.ossep)
789 striplen1 += len(pycompat.ossep)
790 if evalpath(striplen1) > score:
790 if evalpath(striplen1) > score:
791 striplen = striplen1
791 striplen = striplen1
792 res = lambda p: os.path.join(dest,
792 res = lambda p: os.path.join(dest,
793 util.localpath(p)[striplen:])
793 util.localpath(p)[striplen:])
794 else:
794 else:
795 # a file
795 # a file
796 if destdirexists:
796 if destdirexists:
797 res = lambda p: os.path.join(dest,
797 res = lambda p: os.path.join(dest,
798 os.path.basename(util.localpath(p)))
798 os.path.basename(util.localpath(p)))
799 else:
799 else:
800 res = lambda p: dest
800 res = lambda p: dest
801 return res
801 return res
802
802
803 pats = scmutil.expandpats(pats)
803 pats = scmutil.expandpats(pats)
804 if not pats:
804 if not pats:
805 raise error.Abort(_('no source or destination specified'))
805 raise error.Abort(_('no source or destination specified'))
806 if len(pats) == 1:
806 if len(pats) == 1:
807 raise error.Abort(_('no destination specified'))
807 raise error.Abort(_('no destination specified'))
808 dest = pats.pop()
808 dest = pats.pop()
809 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
809 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
810 if not destdirexists:
810 if not destdirexists:
811 if len(pats) > 1 or matchmod.patkind(pats[0]):
811 if len(pats) > 1 or matchmod.patkind(pats[0]):
812 raise error.Abort(_('with multiple sources, destination must be an '
812 raise error.Abort(_('with multiple sources, destination must be an '
813 'existing directory'))
813 'existing directory'))
814 if util.endswithsep(dest):
814 if util.endswithsep(dest):
815 raise error.Abort(_('destination %s is not a directory') % dest)
815 raise error.Abort(_('destination %s is not a directory') % dest)
816
816
817 tfn = targetpathfn
817 tfn = targetpathfn
818 if after:
818 if after:
819 tfn = targetpathafterfn
819 tfn = targetpathafterfn
820 copylist = []
820 copylist = []
821 for pat in pats:
821 for pat in pats:
822 srcs = walkpat(pat)
822 srcs = walkpat(pat)
823 if not srcs:
823 if not srcs:
824 continue
824 continue
825 copylist.append((tfn(pat, dest, srcs), srcs))
825 copylist.append((tfn(pat, dest, srcs), srcs))
826 if not copylist:
826 if not copylist:
827 raise error.Abort(_('no files to copy'))
827 raise error.Abort(_('no files to copy'))
828
828
829 errors = 0
829 errors = 0
830 for targetpath, srcs in copylist:
830 for targetpath, srcs in copylist:
831 for abssrc, relsrc, exact in srcs:
831 for abssrc, relsrc, exact in srcs:
832 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
832 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
833 errors += 1
833 errors += 1
834
834
835 if errors:
835 if errors:
836 ui.warn(_('(consider using --after)\n'))
836 ui.warn(_('(consider using --after)\n'))
837
837
838 return errors != 0
838 return errors != 0
839
839
840 ## facility to let extension process additional data into an import patch
840 ## facility to let extension process additional data into an import patch
841 # list of identifier to be executed in order
841 # list of identifier to be executed in order
842 extrapreimport = [] # run before commit
842 extrapreimport = [] # run before commit
843 extrapostimport = [] # run after commit
843 extrapostimport = [] # run after commit
844 # mapping from identifier to actual import function
844 # mapping from identifier to actual import function
845 #
845 #
846 # 'preimport' are run before the commit is made and are provided the following
846 # 'preimport' are run before the commit is made and are provided the following
847 # arguments:
847 # arguments:
848 # - repo: the localrepository instance,
848 # - repo: the localrepository instance,
849 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
849 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
850 # - extra: the future extra dictionary of the changeset, please mutate it,
850 # - extra: the future extra dictionary of the changeset, please mutate it,
851 # - opts: the import options.
851 # - opts: the import options.
852 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
852 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
853 # mutation of in memory commit and more. Feel free to rework the code to get
853 # mutation of in memory commit and more. Feel free to rework the code to get
854 # there.
854 # there.
855 extrapreimportmap = {}
855 extrapreimportmap = {}
856 # 'postimport' are run after the commit is made and are provided the following
856 # 'postimport' are run after the commit is made and are provided the following
857 # argument:
857 # argument:
858 # - ctx: the changectx created by import.
858 # - ctx: the changectx created by import.
859 extrapostimportmap = {}
859 extrapostimportmap = {}
860
860
861 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
861 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
862 """Utility function used by commands.import to import a single patch
862 """Utility function used by commands.import to import a single patch
863
863
864 This function is explicitly defined here to help the evolve extension to
864 This function is explicitly defined here to help the evolve extension to
865 wrap this part of the import logic.
865 wrap this part of the import logic.
866
866
867 The API is currently a bit ugly because it a simple code translation from
867 The API is currently a bit ugly because it a simple code translation from
868 the import command. Feel free to make it better.
868 the import command. Feel free to make it better.
869
869
870 :hunk: a patch (as a binary string)
870 :hunk: a patch (as a binary string)
871 :parents: nodes that will be parent of the created commit
871 :parents: nodes that will be parent of the created commit
872 :opts: the full dict of option passed to the import command
872 :opts: the full dict of option passed to the import command
873 :msgs: list to save commit message to.
873 :msgs: list to save commit message to.
874 (used in case we need to save it when failing)
874 (used in case we need to save it when failing)
875 :updatefunc: a function that update a repo to a given node
875 :updatefunc: a function that update a repo to a given node
876 updatefunc(<repo>, <node>)
876 updatefunc(<repo>, <node>)
877 """
877 """
878 # avoid cycle context -> subrepo -> cmdutil
878 # avoid cycle context -> subrepo -> cmdutil
879 from . import context
879 from . import context
880 extractdata = patch.extract(ui, hunk)
880 extractdata = patch.extract(ui, hunk)
881 tmpname = extractdata.get('filename')
881 tmpname = extractdata.get('filename')
882 message = extractdata.get('message')
882 message = extractdata.get('message')
883 user = opts.get('user') or extractdata.get('user')
883 user = opts.get('user') or extractdata.get('user')
884 date = opts.get('date') or extractdata.get('date')
884 date = opts.get('date') or extractdata.get('date')
885 branch = extractdata.get('branch')
885 branch = extractdata.get('branch')
886 nodeid = extractdata.get('nodeid')
886 nodeid = extractdata.get('nodeid')
887 p1 = extractdata.get('p1')
887 p1 = extractdata.get('p1')
888 p2 = extractdata.get('p2')
888 p2 = extractdata.get('p2')
889
889
890 nocommit = opts.get('no_commit')
890 nocommit = opts.get('no_commit')
891 importbranch = opts.get('import_branch')
891 importbranch = opts.get('import_branch')
892 update = not opts.get('bypass')
892 update = not opts.get('bypass')
893 strip = opts["strip"]
893 strip = opts["strip"]
894 prefix = opts["prefix"]
894 prefix = opts["prefix"]
895 sim = float(opts.get('similarity') or 0)
895 sim = float(opts.get('similarity') or 0)
896 if not tmpname:
896 if not tmpname:
897 return (None, None, False)
897 return (None, None, False)
898
898
899 rejects = False
899 rejects = False
900
900
901 try:
901 try:
902 cmdline_message = logmessage(ui, opts)
902 cmdline_message = logmessage(ui, opts)
903 if cmdline_message:
903 if cmdline_message:
904 # pickup the cmdline msg
904 # pickup the cmdline msg
905 message = cmdline_message
905 message = cmdline_message
906 elif message:
906 elif message:
907 # pickup the patch msg
907 # pickup the patch msg
908 message = message.strip()
908 message = message.strip()
909 else:
909 else:
910 # launch the editor
910 # launch the editor
911 message = None
911 message = None
912 ui.debug('message:\n%s\n' % message)
912 ui.debug('message:\n%s\n' % message)
913
913
914 if len(parents) == 1:
914 if len(parents) == 1:
915 parents.append(repo[nullid])
915 parents.append(repo[nullid])
916 if opts.get('exact'):
916 if opts.get('exact'):
917 if not nodeid or not p1:
917 if not nodeid or not p1:
918 raise error.Abort(_('not a Mercurial patch'))
918 raise error.Abort(_('not a Mercurial patch'))
919 p1 = repo[p1]
919 p1 = repo[p1]
920 p2 = repo[p2 or nullid]
920 p2 = repo[p2 or nullid]
921 elif p2:
921 elif p2:
922 try:
922 try:
923 p1 = repo[p1]
923 p1 = repo[p1]
924 p2 = repo[p2]
924 p2 = repo[p2]
925 # Without any options, consider p2 only if the
925 # Without any options, consider p2 only if the
926 # patch is being applied on top of the recorded
926 # patch is being applied on top of the recorded
927 # first parent.
927 # first parent.
928 if p1 != parents[0]:
928 if p1 != parents[0]:
929 p1 = parents[0]
929 p1 = parents[0]
930 p2 = repo[nullid]
930 p2 = repo[nullid]
931 except error.RepoError:
931 except error.RepoError:
932 p1, p2 = parents
932 p1, p2 = parents
933 if p2.node() == nullid:
933 if p2.node() == nullid:
934 ui.warn(_("warning: import the patch as a normal revision\n"
934 ui.warn(_("warning: import the patch as a normal revision\n"
935 "(use --exact to import the patch as a merge)\n"))
935 "(use --exact to import the patch as a merge)\n"))
936 else:
936 else:
937 p1, p2 = parents
937 p1, p2 = parents
938
938
939 n = None
939 n = None
940 if update:
940 if update:
941 if p1 != parents[0]:
941 if p1 != parents[0]:
942 updatefunc(repo, p1.node())
942 updatefunc(repo, p1.node())
943 if p2 != parents[1]:
943 if p2 != parents[1]:
944 repo.setparents(p1.node(), p2.node())
944 repo.setparents(p1.node(), p2.node())
945
945
946 if opts.get('exact') or importbranch:
946 if opts.get('exact') or importbranch:
947 repo.dirstate.setbranch(branch or 'default')
947 repo.dirstate.setbranch(branch or 'default')
948
948
949 partial = opts.get('partial', False)
949 partial = opts.get('partial', False)
950 files = set()
950 files = set()
951 try:
951 try:
952 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
952 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
953 files=files, eolmode=None, similarity=sim / 100.0)
953 files=files, eolmode=None, similarity=sim / 100.0)
954 except patch.PatchError as e:
954 except patch.PatchError as e:
955 if not partial:
955 if not partial:
956 raise error.Abort(str(e))
956 raise error.Abort(str(e))
957 if partial:
957 if partial:
958 rejects = True
958 rejects = True
959
959
960 files = list(files)
960 files = list(files)
961 if nocommit:
961 if nocommit:
962 if message:
962 if message:
963 msgs.append(message)
963 msgs.append(message)
964 else:
964 else:
965 if opts.get('exact') or p2:
965 if opts.get('exact') or p2:
966 # If you got here, you either use --force and know what
966 # If you got here, you either use --force and know what
967 # you are doing or used --exact or a merge patch while
967 # you are doing or used --exact or a merge patch while
968 # being updated to its first parent.
968 # being updated to its first parent.
969 m = None
969 m = None
970 else:
970 else:
971 m = scmutil.matchfiles(repo, files or [])
971 m = scmutil.matchfiles(repo, files or [])
972 editform = mergeeditform(repo[None], 'import.normal')
972 editform = mergeeditform(repo[None], 'import.normal')
973 if opts.get('exact'):
973 if opts.get('exact'):
974 editor = None
974 editor = None
975 else:
975 else:
976 editor = getcommiteditor(editform=editform, **opts)
976 editor = getcommiteditor(editform=editform, **opts)
977 extra = {}
977 extra = {}
978 for idfunc in extrapreimport:
978 for idfunc in extrapreimport:
979 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
979 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
980 overrides = {}
980 overrides = {}
981 if partial:
981 if partial:
982 overrides[('ui', 'allowemptycommit')] = True
982 overrides[('ui', 'allowemptycommit')] = True
983 with repo.ui.configoverride(overrides, 'import'):
983 with repo.ui.configoverride(overrides, 'import'):
984 n = repo.commit(message, user,
984 n = repo.commit(message, user,
985 date, match=m,
985 date, match=m,
986 editor=editor, extra=extra)
986 editor=editor, extra=extra)
987 for idfunc in extrapostimport:
987 for idfunc in extrapostimport:
988 extrapostimportmap[idfunc](repo[n])
988 extrapostimportmap[idfunc](repo[n])
989 else:
989 else:
990 if opts.get('exact') or importbranch:
990 if opts.get('exact') or importbranch:
991 branch = branch or 'default'
991 branch = branch or 'default'
992 else:
992 else:
993 branch = p1.branch()
993 branch = p1.branch()
994 store = patch.filestore()
994 store = patch.filestore()
995 try:
995 try:
996 files = set()
996 files = set()
997 try:
997 try:
998 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
998 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
999 files, eolmode=None)
999 files, eolmode=None)
1000 except patch.PatchError as e:
1000 except patch.PatchError as e:
1001 raise error.Abort(str(e))
1001 raise error.Abort(str(e))
1002 if opts.get('exact'):
1002 if opts.get('exact'):
1003 editor = None
1003 editor = None
1004 else:
1004 else:
1005 editor = getcommiteditor(editform='import.bypass')
1005 editor = getcommiteditor(editform='import.bypass')
1006 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1006 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1007 message,
1007 message,
1008 user,
1008 user,
1009 date,
1009 date,
1010 branch, files, store,
1010 branch, files, store,
1011 editor=editor)
1011 editor=editor)
1012 n = memctx.commit()
1012 n = memctx.commit()
1013 finally:
1013 finally:
1014 store.close()
1014 store.close()
1015 if opts.get('exact') and nocommit:
1015 if opts.get('exact') and nocommit:
1016 # --exact with --no-commit is still useful in that it does merge
1016 # --exact with --no-commit is still useful in that it does merge
1017 # and branch bits
1017 # and branch bits
1018 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1018 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1019 elif opts.get('exact') and hex(n) != nodeid:
1019 elif opts.get('exact') and hex(n) != nodeid:
1020 raise error.Abort(_('patch is damaged or loses information'))
1020 raise error.Abort(_('patch is damaged or loses information'))
1021 msg = _('applied to working directory')
1021 msg = _('applied to working directory')
1022 if n:
1022 if n:
1023 # i18n: refers to a short changeset id
1023 # i18n: refers to a short changeset id
1024 msg = _('created %s') % short(n)
1024 msg = _('created %s') % short(n)
1025 return (msg, n, rejects)
1025 return (msg, n, rejects)
1026 finally:
1026 finally:
1027 os.unlink(tmpname)
1027 os.unlink(tmpname)
1028
1028
1029 # facility to let extensions include additional data in an exported patch
1029 # facility to let extensions include additional data in an exported patch
1030 # list of identifiers to be executed in order
1030 # list of identifiers to be executed in order
1031 extraexport = []
1031 extraexport = []
1032 # mapping from identifier to actual export function
1032 # mapping from identifier to actual export function
1033 # function as to return a string to be added to the header or None
1033 # function as to return a string to be added to the header or None
1034 # it is given two arguments (sequencenumber, changectx)
1034 # it is given two arguments (sequencenumber, changectx)
1035 extraexportmap = {}
1035 extraexportmap = {}
1036
1036
1037 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1037 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1038 opts=None, match=None):
1038 opts=None, match=None):
1039 '''export changesets as hg patches.'''
1039 '''export changesets as hg patches.'''
1040
1040
1041 total = len(revs)
1041 total = len(revs)
1042 revwidth = max([len(str(rev)) for rev in revs])
1042 revwidth = max([len(str(rev)) for rev in revs])
1043 filemode = {}
1043 filemode = {}
1044
1044
1045 def single(rev, seqno, fp):
1045 def single(rev, seqno, fp):
1046 ctx = repo[rev]
1046 ctx = repo[rev]
1047 node = ctx.node()
1047 node = ctx.node()
1048 parents = [p.node() for p in ctx.parents() if p]
1048 parents = [p.node() for p in ctx.parents() if p]
1049 branch = ctx.branch()
1049 branch = ctx.branch()
1050 if switch_parent:
1050 if switch_parent:
1051 parents.reverse()
1051 parents.reverse()
1052
1052
1053 if parents:
1053 if parents:
1054 prev = parents[0]
1054 prev = parents[0]
1055 else:
1055 else:
1056 prev = nullid
1056 prev = nullid
1057
1057
1058 shouldclose = False
1058 shouldclose = False
1059 if not fp and len(template) > 0:
1059 if not fp and len(template) > 0:
1060 desc_lines = ctx.description().rstrip().split('\n')
1060 desc_lines = ctx.description().rstrip().split('\n')
1061 desc = desc_lines[0] #Commit always has a first line.
1061 desc = desc_lines[0] #Commit always has a first line.
1062 fp = makefileobj(repo, template, node, desc=desc, total=total,
1062 fp = makefileobj(repo, template, node, desc=desc, total=total,
1063 seqno=seqno, revwidth=revwidth, mode='wb',
1063 seqno=seqno, revwidth=revwidth, mode='wb',
1064 modemap=filemode)
1064 modemap=filemode)
1065 shouldclose = True
1065 shouldclose = True
1066 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1066 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1067 repo.ui.note("%s\n" % fp.name)
1067 repo.ui.note("%s\n" % fp.name)
1068
1068
1069 if not fp:
1069 if not fp:
1070 write = repo.ui.write
1070 write = repo.ui.write
1071 else:
1071 else:
1072 def write(s, **kw):
1072 def write(s, **kw):
1073 fp.write(s)
1073 fp.write(s)
1074
1074
1075 write("# HG changeset patch\n")
1075 write("# HG changeset patch\n")
1076 write("# User %s\n" % ctx.user())
1076 write("# User %s\n" % ctx.user())
1077 write("# Date %d %d\n" % ctx.date())
1077 write("# Date %d %d\n" % ctx.date())
1078 write("# %s\n" % util.datestr(ctx.date()))
1078 write("# %s\n" % util.datestr(ctx.date()))
1079 if branch and branch != 'default':
1079 if branch and branch != 'default':
1080 write("# Branch %s\n" % branch)
1080 write("# Branch %s\n" % branch)
1081 write("# Node ID %s\n" % hex(node))
1081 write("# Node ID %s\n" % hex(node))
1082 write("# Parent %s\n" % hex(prev))
1082 write("# Parent %s\n" % hex(prev))
1083 if len(parents) > 1:
1083 if len(parents) > 1:
1084 write("# Parent %s\n" % hex(parents[1]))
1084 write("# Parent %s\n" % hex(parents[1]))
1085
1085
1086 for headerid in extraexport:
1086 for headerid in extraexport:
1087 header = extraexportmap[headerid](seqno, ctx)
1087 header = extraexportmap[headerid](seqno, ctx)
1088 if header is not None:
1088 if header is not None:
1089 write('# %s\n' % header)
1089 write('# %s\n' % header)
1090 write(ctx.description().rstrip())
1090 write(ctx.description().rstrip())
1091 write("\n\n")
1091 write("\n\n")
1092
1092
1093 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1093 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1094 write(chunk, label=label)
1094 write(chunk, label=label)
1095
1095
1096 if shouldclose:
1096 if shouldclose:
1097 fp.close()
1097 fp.close()
1098
1098
1099 for seqno, rev in enumerate(revs):
1099 for seqno, rev in enumerate(revs):
1100 single(rev, seqno + 1, fp)
1100 single(rev, seqno + 1, fp)
1101
1101
1102 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1102 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1103 changes=None, stat=False, fp=None, prefix='',
1103 changes=None, stat=False, fp=None, prefix='',
1104 root='', listsubrepos=False):
1104 root='', listsubrepos=False):
1105 '''show diff or diffstat.'''
1105 '''show diff or diffstat.'''
1106 if fp is None:
1106 if fp is None:
1107 write = ui.write
1107 write = ui.write
1108 else:
1108 else:
1109 def write(s, **kw):
1109 def write(s, **kw):
1110 fp.write(s)
1110 fp.write(s)
1111
1111
1112 if root:
1112 if root:
1113 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1113 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1114 else:
1114 else:
1115 relroot = ''
1115 relroot = ''
1116 if relroot != '':
1116 if relroot != '':
1117 # XXX relative roots currently don't work if the root is within a
1117 # XXX relative roots currently don't work if the root is within a
1118 # subrepo
1118 # subrepo
1119 uirelroot = match.uipath(relroot)
1119 uirelroot = match.uipath(relroot)
1120 relroot += '/'
1120 relroot += '/'
1121 for matchroot in match.files():
1121 for matchroot in match.files():
1122 if not matchroot.startswith(relroot):
1122 if not matchroot.startswith(relroot):
1123 ui.warn(_('warning: %s not inside relative root %s\n') % (
1123 ui.warn(_('warning: %s not inside relative root %s\n') % (
1124 match.uipath(matchroot), uirelroot))
1124 match.uipath(matchroot), uirelroot))
1125
1125
1126 if stat:
1126 if stat:
1127 diffopts = diffopts.copy(context=0)
1127 diffopts = diffopts.copy(context=0)
1128 width = 80
1128 width = 80
1129 if not ui.plain():
1129 if not ui.plain():
1130 width = ui.termwidth()
1130 width = ui.termwidth()
1131 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1131 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1132 prefix=prefix, relroot=relroot)
1132 prefix=prefix, relroot=relroot)
1133 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1133 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1134 width=width):
1134 width=width):
1135 write(chunk, label=label)
1135 write(chunk, label=label)
1136 else:
1136 else:
1137 for chunk, label in patch.diffui(repo, node1, node2, match,
1137 for chunk, label in patch.diffui(repo, node1, node2, match,
1138 changes, diffopts, prefix=prefix,
1138 changes, diffopts, prefix=prefix,
1139 relroot=relroot):
1139 relroot=relroot):
1140 write(chunk, label=label)
1140 write(chunk, label=label)
1141
1141
1142 if listsubrepos:
1142 if listsubrepos:
1143 ctx1 = repo[node1]
1143 ctx1 = repo[node1]
1144 ctx2 = repo[node2]
1144 ctx2 = repo[node2]
1145 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1145 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1146 tempnode2 = node2
1146 tempnode2 = node2
1147 try:
1147 try:
1148 if node2 is not None:
1148 if node2 is not None:
1149 tempnode2 = ctx2.substate[subpath][1]
1149 tempnode2 = ctx2.substate[subpath][1]
1150 except KeyError:
1150 except KeyError:
1151 # A subrepo that existed in node1 was deleted between node1 and
1151 # A subrepo that existed in node1 was deleted between node1 and
1152 # node2 (inclusive). Thus, ctx2's substate won't contain that
1152 # node2 (inclusive). Thus, ctx2's substate won't contain that
1153 # subpath. The best we can do is to ignore it.
1153 # subpath. The best we can do is to ignore it.
1154 tempnode2 = None
1154 tempnode2 = None
1155 submatch = matchmod.subdirmatcher(subpath, match)
1155 submatch = matchmod.subdirmatcher(subpath, match)
1156 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1156 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1157 stat=stat, fp=fp, prefix=prefix)
1157 stat=stat, fp=fp, prefix=prefix)
1158
1158
1159 def _changesetlabels(ctx):
1159 def _changesetlabels(ctx):
1160 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1160 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1161 if ctx.obsolete():
1161 if ctx.obsolete():
1162 labels.append('changeset.obsolete')
1162 labels.append('changeset.obsolete')
1163 if ctx.troubled():
1163 if ctx.troubled():
1164 labels.append('changeset.troubled')
1164 labels.append('changeset.troubled')
1165 for trouble in ctx.troubles():
1165 for trouble in ctx.troubles():
1166 labels.append('trouble.%s' % trouble)
1166 labels.append('trouble.%s' % trouble)
1167 return ' '.join(labels)
1167 return ' '.join(labels)
1168
1168
1169 class changeset_printer(object):
1169 class changeset_printer(object):
1170 '''show changeset information when templating not requested.'''
1170 '''show changeset information when templating not requested.'''
1171
1171
1172 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1172 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1173 self.ui = ui
1173 self.ui = ui
1174 self.repo = repo
1174 self.repo = repo
1175 self.buffered = buffered
1175 self.buffered = buffered
1176 self.matchfn = matchfn
1176 self.matchfn = matchfn
1177 self.diffopts = diffopts
1177 self.diffopts = diffopts
1178 self.header = {}
1178 self.header = {}
1179 self.hunk = {}
1179 self.hunk = {}
1180 self.lastheader = None
1180 self.lastheader = None
1181 self.footer = None
1181 self.footer = None
1182
1182
1183 def flush(self, ctx):
1183 def flush(self, ctx):
1184 rev = ctx.rev()
1184 rev = ctx.rev()
1185 if rev in self.header:
1185 if rev in self.header:
1186 h = self.header[rev]
1186 h = self.header[rev]
1187 if h != self.lastheader:
1187 if h != self.lastheader:
1188 self.lastheader = h
1188 self.lastheader = h
1189 self.ui.write(h)
1189 self.ui.write(h)
1190 del self.header[rev]
1190 del self.header[rev]
1191 if rev in self.hunk:
1191 if rev in self.hunk:
1192 self.ui.write(self.hunk[rev])
1192 self.ui.write(self.hunk[rev])
1193 del self.hunk[rev]
1193 del self.hunk[rev]
1194 return 1
1194 return 1
1195 return 0
1195 return 0
1196
1196
1197 def close(self):
1197 def close(self):
1198 if self.footer:
1198 if self.footer:
1199 self.ui.write(self.footer)
1199 self.ui.write(self.footer)
1200
1200
1201 def show(self, ctx, copies=None, matchfn=None, **props):
1201 def show(self, ctx, copies=None, matchfn=None, **props):
1202 if self.buffered:
1202 if self.buffered:
1203 self.ui.pushbuffer(labeled=True)
1203 self.ui.pushbuffer(labeled=True)
1204 self._show(ctx, copies, matchfn, props)
1204 self._show(ctx, copies, matchfn, props)
1205 self.hunk[ctx.rev()] = self.ui.popbuffer()
1205 self.hunk[ctx.rev()] = self.ui.popbuffer()
1206 else:
1206 else:
1207 self._show(ctx, copies, matchfn, props)
1207 self._show(ctx, copies, matchfn, props)
1208
1208
1209 def _show(self, ctx, copies, matchfn, props):
1209 def _show(self, ctx, copies, matchfn, props):
1210 '''show a single changeset or file revision'''
1210 '''show a single changeset or file revision'''
1211 changenode = ctx.node()
1211 changenode = ctx.node()
1212 rev = ctx.rev()
1212 rev = ctx.rev()
1213 if self.ui.debugflag:
1213 if self.ui.debugflag:
1214 hexfunc = hex
1214 hexfunc = hex
1215 else:
1215 else:
1216 hexfunc = short
1216 hexfunc = short
1217 # as of now, wctx.node() and wctx.rev() return None, but we want to
1217 # as of now, wctx.node() and wctx.rev() return None, but we want to
1218 # show the same values as {node} and {rev} templatekw
1218 # show the same values as {node} and {rev} templatekw
1219 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1219 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1220
1220
1221 if self.ui.quiet:
1221 if self.ui.quiet:
1222 self.ui.write("%d:%s\n" % revnode, label='log.node')
1222 self.ui.write("%d:%s\n" % revnode, label='log.node')
1223 return
1223 return
1224
1224
1225 date = util.datestr(ctx.date())
1225 date = util.datestr(ctx.date())
1226
1226
1227 # i18n: column positioning for "hg log"
1227 # i18n: column positioning for "hg log"
1228 self.ui.write(_("changeset: %d:%s\n") % revnode,
1228 self.ui.write(_("changeset: %d:%s\n") % revnode,
1229 label=_changesetlabels(ctx))
1229 label=_changesetlabels(ctx))
1230
1230
1231 # branches are shown first before any other names due to backwards
1231 # branches are shown first before any other names due to backwards
1232 # compatibility
1232 # compatibility
1233 branch = ctx.branch()
1233 branch = ctx.branch()
1234 # don't show the default branch name
1234 # don't show the default branch name
1235 if branch != 'default':
1235 if branch != 'default':
1236 # i18n: column positioning for "hg log"
1236 # i18n: column positioning for "hg log"
1237 self.ui.write(_("branch: %s\n") % branch,
1237 self.ui.write(_("branch: %s\n") % branch,
1238 label='log.branch')
1238 label='log.branch')
1239
1239
1240 for nsname, ns in self.repo.names.iteritems():
1240 for nsname, ns in self.repo.names.iteritems():
1241 # branches has special logic already handled above, so here we just
1241 # branches has special logic already handled above, so here we just
1242 # skip it
1242 # skip it
1243 if nsname == 'branches':
1243 if nsname == 'branches':
1244 continue
1244 continue
1245 # we will use the templatename as the color name since those two
1245 # we will use the templatename as the color name since those two
1246 # should be the same
1246 # should be the same
1247 for name in ns.names(self.repo, changenode):
1247 for name in ns.names(self.repo, changenode):
1248 self.ui.write(ns.logfmt % name,
1248 self.ui.write(ns.logfmt % name,
1249 label='log.%s' % ns.colorname)
1249 label='log.%s' % ns.colorname)
1250 if self.ui.debugflag:
1250 if self.ui.debugflag:
1251 # i18n: column positioning for "hg log"
1251 # i18n: column positioning for "hg log"
1252 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1252 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1253 label='log.phase')
1253 label='log.phase')
1254 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1254 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1255 label = 'log.parent changeset.%s' % pctx.phasestr()
1255 label = 'log.parent changeset.%s' % pctx.phasestr()
1256 # i18n: column positioning for "hg log"
1256 # i18n: column positioning for "hg log"
1257 self.ui.write(_("parent: %d:%s\n")
1257 self.ui.write(_("parent: %d:%s\n")
1258 % (pctx.rev(), hexfunc(pctx.node())),
1258 % (pctx.rev(), hexfunc(pctx.node())),
1259 label=label)
1259 label=label)
1260
1260
1261 if self.ui.debugflag and rev is not None:
1261 if self.ui.debugflag and rev is not None:
1262 mnode = ctx.manifestnode()
1262 mnode = ctx.manifestnode()
1263 # i18n: column positioning for "hg log"
1263 # i18n: column positioning for "hg log"
1264 self.ui.write(_("manifest: %d:%s\n") %
1264 self.ui.write(_("manifest: %d:%s\n") %
1265 (self.repo.manifestlog._revlog.rev(mnode),
1265 (self.repo.manifestlog._revlog.rev(mnode),
1266 hex(mnode)),
1266 hex(mnode)),
1267 label='ui.debug log.manifest')
1267 label='ui.debug log.manifest')
1268 # i18n: column positioning for "hg log"
1268 # i18n: column positioning for "hg log"
1269 self.ui.write(_("user: %s\n") % ctx.user(),
1269 self.ui.write(_("user: %s\n") % ctx.user(),
1270 label='log.user')
1270 label='log.user')
1271 # i18n: column positioning for "hg log"
1271 # i18n: column positioning for "hg log"
1272 self.ui.write(_("date: %s\n") % date,
1272 self.ui.write(_("date: %s\n") % date,
1273 label='log.date')
1273 label='log.date')
1274
1274
1275 if ctx.troubled():
1275 if ctx.troubled():
1276 # i18n: column positioning for "hg log"
1276 # i18n: column positioning for "hg log"
1277 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1277 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1278 label='log.trouble')
1278 label='log.trouble')
1279
1279
1280 if self.ui.debugflag:
1280 if self.ui.debugflag:
1281 files = ctx.p1().status(ctx)[:3]
1281 files = ctx.p1().status(ctx)[:3]
1282 for key, value in zip([# i18n: column positioning for "hg log"
1282 for key, value in zip([# i18n: column positioning for "hg log"
1283 _("files:"),
1283 _("files:"),
1284 # i18n: column positioning for "hg log"
1284 # i18n: column positioning for "hg log"
1285 _("files+:"),
1285 _("files+:"),
1286 # i18n: column positioning for "hg log"
1286 # i18n: column positioning for "hg log"
1287 _("files-:")], files):
1287 _("files-:")], files):
1288 if value:
1288 if value:
1289 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1289 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1290 label='ui.debug log.files')
1290 label='ui.debug log.files')
1291 elif ctx.files() and self.ui.verbose:
1291 elif ctx.files() and self.ui.verbose:
1292 # i18n: column positioning for "hg log"
1292 # i18n: column positioning for "hg log"
1293 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1293 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1294 label='ui.note log.files')
1294 label='ui.note log.files')
1295 if copies and self.ui.verbose:
1295 if copies and self.ui.verbose:
1296 copies = ['%s (%s)' % c for c in copies]
1296 copies = ['%s (%s)' % c for c in copies]
1297 # i18n: column positioning for "hg log"
1297 # i18n: column positioning for "hg log"
1298 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1298 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1299 label='ui.note log.copies')
1299 label='ui.note log.copies')
1300
1300
1301 extra = ctx.extra()
1301 extra = ctx.extra()
1302 if extra and self.ui.debugflag:
1302 if extra and self.ui.debugflag:
1303 for key, value in sorted(extra.items()):
1303 for key, value in sorted(extra.items()):
1304 # i18n: column positioning for "hg log"
1304 # i18n: column positioning for "hg log"
1305 self.ui.write(_("extra: %s=%s\n")
1305 self.ui.write(_("extra: %s=%s\n")
1306 % (key, util.escapestr(value)),
1306 % (key, util.escapestr(value)),
1307 label='ui.debug log.extra')
1307 label='ui.debug log.extra')
1308
1308
1309 description = ctx.description().strip()
1309 description = ctx.description().strip()
1310 if description:
1310 if description:
1311 if self.ui.verbose:
1311 if self.ui.verbose:
1312 self.ui.write(_("description:\n"),
1312 self.ui.write(_("description:\n"),
1313 label='ui.note log.description')
1313 label='ui.note log.description')
1314 self.ui.write(description,
1314 self.ui.write(description,
1315 label='ui.note log.description')
1315 label='ui.note log.description')
1316 self.ui.write("\n\n")
1316 self.ui.write("\n\n")
1317 else:
1317 else:
1318 # i18n: column positioning for "hg log"
1318 # i18n: column positioning for "hg log"
1319 self.ui.write(_("summary: %s\n") %
1319 self.ui.write(_("summary: %s\n") %
1320 description.splitlines()[0],
1320 description.splitlines()[0],
1321 label='log.summary')
1321 label='log.summary')
1322 self.ui.write("\n")
1322 self.ui.write("\n")
1323
1323
1324 self.showpatch(ctx, matchfn)
1324 self.showpatch(ctx, matchfn)
1325
1325
1326 def showpatch(self, ctx, matchfn):
1326 def showpatch(self, ctx, matchfn):
1327 if not matchfn:
1327 if not matchfn:
1328 matchfn = self.matchfn
1328 matchfn = self.matchfn
1329 if matchfn:
1329 if matchfn:
1330 stat = self.diffopts.get('stat')
1330 stat = self.diffopts.get('stat')
1331 diff = self.diffopts.get('patch')
1331 diff = self.diffopts.get('patch')
1332 diffopts = patch.diffallopts(self.ui, self.diffopts)
1332 diffopts = patch.diffallopts(self.ui, self.diffopts)
1333 node = ctx.node()
1333 node = ctx.node()
1334 prev = ctx.p1().node()
1334 prev = ctx.p1().node()
1335 if stat:
1335 if stat:
1336 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1336 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1337 match=matchfn, stat=True)
1337 match=matchfn, stat=True)
1338 if diff:
1338 if diff:
1339 if stat:
1339 if stat:
1340 self.ui.write("\n")
1340 self.ui.write("\n")
1341 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1341 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1342 match=matchfn, stat=False)
1342 match=matchfn, stat=False)
1343 self.ui.write("\n")
1343 self.ui.write("\n")
1344
1344
1345 class jsonchangeset(changeset_printer):
1345 class jsonchangeset(changeset_printer):
1346 '''format changeset information.'''
1346 '''format changeset information.'''
1347
1347
1348 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1348 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1349 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1349 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1350 self.cache = {}
1350 self.cache = {}
1351 self._first = True
1351 self._first = True
1352
1352
1353 def close(self):
1353 def close(self):
1354 if not self._first:
1354 if not self._first:
1355 self.ui.write("\n]\n")
1355 self.ui.write("\n]\n")
1356 else:
1356 else:
1357 self.ui.write("[]\n")
1357 self.ui.write("[]\n")
1358
1358
1359 def _show(self, ctx, copies, matchfn, props):
1359 def _show(self, ctx, copies, matchfn, props):
1360 '''show a single changeset or file revision'''
1360 '''show a single changeset or file revision'''
1361 rev = ctx.rev()
1361 rev = ctx.rev()
1362 if rev is None:
1362 if rev is None:
1363 jrev = jnode = 'null'
1363 jrev = jnode = 'null'
1364 else:
1364 else:
1365 jrev = '%d' % rev
1365 jrev = '%d' % rev
1366 jnode = '"%s"' % hex(ctx.node())
1366 jnode = '"%s"' % hex(ctx.node())
1367 j = encoding.jsonescape
1367 j = encoding.jsonescape
1368
1368
1369 if self._first:
1369 if self._first:
1370 self.ui.write("[\n {")
1370 self.ui.write("[\n {")
1371 self._first = False
1371 self._first = False
1372 else:
1372 else:
1373 self.ui.write(",\n {")
1373 self.ui.write(",\n {")
1374
1374
1375 if self.ui.quiet:
1375 if self.ui.quiet:
1376 self.ui.write(('\n "rev": %s') % jrev)
1376 self.ui.write(('\n "rev": %s') % jrev)
1377 self.ui.write((',\n "node": %s') % jnode)
1377 self.ui.write((',\n "node": %s') % jnode)
1378 self.ui.write('\n }')
1378 self.ui.write('\n }')
1379 return
1379 return
1380
1380
1381 self.ui.write(('\n "rev": %s') % jrev)
1381 self.ui.write(('\n "rev": %s') % jrev)
1382 self.ui.write((',\n "node": %s') % jnode)
1382 self.ui.write((',\n "node": %s') % jnode)
1383 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1383 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1384 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1384 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1385 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1385 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1386 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1386 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1387 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1387 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1388
1388
1389 self.ui.write((',\n "bookmarks": [%s]') %
1389 self.ui.write((',\n "bookmarks": [%s]') %
1390 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1390 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1391 self.ui.write((',\n "tags": [%s]') %
1391 self.ui.write((',\n "tags": [%s]') %
1392 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1392 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1393 self.ui.write((',\n "parents": [%s]') %
1393 self.ui.write((',\n "parents": [%s]') %
1394 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1394 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1395
1395
1396 if self.ui.debugflag:
1396 if self.ui.debugflag:
1397 if rev is None:
1397 if rev is None:
1398 jmanifestnode = 'null'
1398 jmanifestnode = 'null'
1399 else:
1399 else:
1400 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1400 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1401 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1401 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1402
1402
1403 self.ui.write((',\n "extra": {%s}') %
1403 self.ui.write((',\n "extra": {%s}') %
1404 ", ".join('"%s": "%s"' % (j(k), j(v))
1404 ", ".join('"%s": "%s"' % (j(k), j(v))
1405 for k, v in ctx.extra().items()))
1405 for k, v in ctx.extra().items()))
1406
1406
1407 files = ctx.p1().status(ctx)
1407 files = ctx.p1().status(ctx)
1408 self.ui.write((',\n "modified": [%s]') %
1408 self.ui.write((',\n "modified": [%s]') %
1409 ", ".join('"%s"' % j(f) for f in files[0]))
1409 ", ".join('"%s"' % j(f) for f in files[0]))
1410 self.ui.write((',\n "added": [%s]') %
1410 self.ui.write((',\n "added": [%s]') %
1411 ", ".join('"%s"' % j(f) for f in files[1]))
1411 ", ".join('"%s"' % j(f) for f in files[1]))
1412 self.ui.write((',\n "removed": [%s]') %
1412 self.ui.write((',\n "removed": [%s]') %
1413 ", ".join('"%s"' % j(f) for f in files[2]))
1413 ", ".join('"%s"' % j(f) for f in files[2]))
1414
1414
1415 elif self.ui.verbose:
1415 elif self.ui.verbose:
1416 self.ui.write((',\n "files": [%s]') %
1416 self.ui.write((',\n "files": [%s]') %
1417 ", ".join('"%s"' % j(f) for f in ctx.files()))
1417 ", ".join('"%s"' % j(f) for f in ctx.files()))
1418
1418
1419 if copies:
1419 if copies:
1420 self.ui.write((',\n "copies": {%s}') %
1420 self.ui.write((',\n "copies": {%s}') %
1421 ", ".join('"%s": "%s"' % (j(k), j(v))
1421 ", ".join('"%s": "%s"' % (j(k), j(v))
1422 for k, v in copies))
1422 for k, v in copies))
1423
1423
1424 matchfn = self.matchfn
1424 matchfn = self.matchfn
1425 if matchfn:
1425 if matchfn:
1426 stat = self.diffopts.get('stat')
1426 stat = self.diffopts.get('stat')
1427 diff = self.diffopts.get('patch')
1427 diff = self.diffopts.get('patch')
1428 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1428 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1429 node, prev = ctx.node(), ctx.p1().node()
1429 node, prev = ctx.node(), ctx.p1().node()
1430 if stat:
1430 if stat:
1431 self.ui.pushbuffer()
1431 self.ui.pushbuffer()
1432 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1432 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1433 match=matchfn, stat=True)
1433 match=matchfn, stat=True)
1434 self.ui.write((',\n "diffstat": "%s"')
1434 self.ui.write((',\n "diffstat": "%s"')
1435 % j(self.ui.popbuffer()))
1435 % j(self.ui.popbuffer()))
1436 if diff:
1436 if diff:
1437 self.ui.pushbuffer()
1437 self.ui.pushbuffer()
1438 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1438 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1439 match=matchfn, stat=False)
1439 match=matchfn, stat=False)
1440 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1440 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1441
1441
1442 self.ui.write("\n }")
1442 self.ui.write("\n }")
1443
1443
1444 class changeset_templater(changeset_printer):
1444 class changeset_templater(changeset_printer):
1445 '''format changeset information.'''
1445 '''format changeset information.'''
1446
1446
1447 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1447 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1448 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1448 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1449 assert not (tmpl and mapfile)
1449 assert not (tmpl and mapfile)
1450 defaulttempl = templatekw.defaulttempl
1450 defaulttempl = templatekw.defaulttempl
1451 if mapfile:
1451 if mapfile:
1452 self.t = templater.templater.frommapfile(mapfile,
1452 self.t = templater.templater.frommapfile(mapfile,
1453 cache=defaulttempl)
1453 cache=defaulttempl)
1454 else:
1454 else:
1455 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1455 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1456 cache=defaulttempl)
1456 cache=defaulttempl)
1457
1457
1458 self._counter = itertools.count()
1458 self._counter = itertools.count()
1459 self.cache = {}
1459 self.cache = {}
1460
1460
1461 # find correct templates for current mode
1461 # find correct templates for current mode
1462 tmplmodes = [
1462 tmplmodes = [
1463 (True, None),
1463 (True, None),
1464 (self.ui.verbose, 'verbose'),
1464 (self.ui.verbose, 'verbose'),
1465 (self.ui.quiet, 'quiet'),
1465 (self.ui.quiet, 'quiet'),
1466 (self.ui.debugflag, 'debug'),
1466 (self.ui.debugflag, 'debug'),
1467 ]
1467 ]
1468
1468
1469 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1469 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1470 'docheader': '', 'docfooter': ''}
1470 'docheader': '', 'docfooter': ''}
1471 for mode, postfix in tmplmodes:
1471 for mode, postfix in tmplmodes:
1472 for t in self._parts:
1472 for t in self._parts:
1473 cur = t
1473 cur = t
1474 if postfix:
1474 if postfix:
1475 cur += "_" + postfix
1475 cur += "_" + postfix
1476 if mode and cur in self.t:
1476 if mode and cur in self.t:
1477 self._parts[t] = cur
1477 self._parts[t] = cur
1478
1478
1479 if self._parts['docheader']:
1479 if self._parts['docheader']:
1480 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1480 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1481
1481
1482 def close(self):
1482 def close(self):
1483 if self._parts['docfooter']:
1483 if self._parts['docfooter']:
1484 if not self.footer:
1484 if not self.footer:
1485 self.footer = ""
1485 self.footer = ""
1486 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1486 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1487 return super(changeset_templater, self).close()
1487 return super(changeset_templater, self).close()
1488
1488
1489 def _show(self, ctx, copies, matchfn, props):
1489 def _show(self, ctx, copies, matchfn, props):
1490 '''show a single changeset or file revision'''
1490 '''show a single changeset or file revision'''
1491 props = props.copy()
1491 props = props.copy()
1492 props.update(templatekw.keywords)
1492 props.update(templatekw.keywords)
1493 props['templ'] = self.t
1493 props['templ'] = self.t
1494 props['ctx'] = ctx
1494 props['ctx'] = ctx
1495 props['repo'] = self.repo
1495 props['repo'] = self.repo
1496 props['ui'] = self.repo.ui
1496 props['ui'] = self.repo.ui
1497 props['index'] = next(self._counter)
1497 props['index'] = next(self._counter)
1498 props['revcache'] = {'copies': copies}
1498 props['revcache'] = {'copies': copies}
1499 props['cache'] = self.cache
1499 props['cache'] = self.cache
1500 props = pycompat.strkwargs(props)
1500 props = pycompat.strkwargs(props)
1501
1501
1502 # write header
1502 # write header
1503 if self._parts['header']:
1503 if self._parts['header']:
1504 h = templater.stringify(self.t(self._parts['header'], **props))
1504 h = templater.stringify(self.t(self._parts['header'], **props))
1505 if self.buffered:
1505 if self.buffered:
1506 self.header[ctx.rev()] = h
1506 self.header[ctx.rev()] = h
1507 else:
1507 else:
1508 if self.lastheader != h:
1508 if self.lastheader != h:
1509 self.lastheader = h
1509 self.lastheader = h
1510 self.ui.write(h)
1510 self.ui.write(h)
1511
1511
1512 # write changeset metadata, then patch if requested
1512 # write changeset metadata, then patch if requested
1513 key = self._parts['changeset']
1513 key = self._parts['changeset']
1514 self.ui.write(templater.stringify(self.t(key, **props)))
1514 self.ui.write(templater.stringify(self.t(key, **props)))
1515 self.showpatch(ctx, matchfn)
1515 self.showpatch(ctx, matchfn)
1516
1516
1517 if self._parts['footer']:
1517 if self._parts['footer']:
1518 if not self.footer:
1518 if not self.footer:
1519 self.footer = templater.stringify(
1519 self.footer = templater.stringify(
1520 self.t(self._parts['footer'], **props))
1520 self.t(self._parts['footer'], **props))
1521
1521
1522 def gettemplate(ui, tmpl, style):
1522 def gettemplate(ui, tmpl, style):
1523 """
1523 """
1524 Find the template matching the given template spec or style.
1524 Find the template matching the given template spec or style.
1525 """
1525 """
1526
1526
1527 # ui settings
1527 # ui settings
1528 if not tmpl and not style: # template are stronger than style
1528 if not tmpl and not style: # template are stronger than style
1529 tmpl = ui.config('ui', 'logtemplate')
1529 tmpl = ui.config('ui', 'logtemplate')
1530 if tmpl:
1530 if tmpl:
1531 return templater.unquotestring(tmpl), None
1531 return templater.unquotestring(tmpl), None
1532 else:
1532 else:
1533 style = util.expandpath(ui.config('ui', 'style', ''))
1533 style = util.expandpath(ui.config('ui', 'style', ''))
1534
1534
1535 if not tmpl and style:
1535 if not tmpl and style:
1536 mapfile = style
1536 mapfile = style
1537 if not os.path.split(mapfile)[0]:
1537 if not os.path.split(mapfile)[0]:
1538 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1538 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1539 or templater.templatepath(mapfile))
1539 or templater.templatepath(mapfile))
1540 if mapname:
1540 if mapname:
1541 mapfile = mapname
1541 mapfile = mapname
1542 return None, mapfile
1542 return None, mapfile
1543
1543
1544 if not tmpl:
1544 if not tmpl:
1545 return None, None
1545 return None, None
1546
1546
1547 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1547 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1548
1548
1549 def show_changeset(ui, repo, opts, buffered=False):
1549 def show_changeset(ui, repo, opts, buffered=False):
1550 """show one changeset using template or regular display.
1550 """show one changeset using template or regular display.
1551
1551
1552 Display format will be the first non-empty hit of:
1552 Display format will be the first non-empty hit of:
1553 1. option 'template'
1553 1. option 'template'
1554 2. option 'style'
1554 2. option 'style'
1555 3. [ui] setting 'logtemplate'
1555 3. [ui] setting 'logtemplate'
1556 4. [ui] setting 'style'
1556 4. [ui] setting 'style'
1557 If all of these values are either the unset or the empty string,
1557 If all of these values are either the unset or the empty string,
1558 regular display via changeset_printer() is done.
1558 regular display via changeset_printer() is done.
1559 """
1559 """
1560 # options
1560 # options
1561 matchfn = None
1561 matchfn = None
1562 if opts.get('patch') or opts.get('stat'):
1562 if opts.get('patch') or opts.get('stat'):
1563 matchfn = scmutil.matchall(repo)
1563 matchfn = scmutil.matchall(repo)
1564
1564
1565 if opts.get('template') == 'json':
1565 if opts.get('template') == 'json':
1566 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1566 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1567
1567
1568 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1568 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1569
1569
1570 if not tmpl and not mapfile:
1570 if not tmpl and not mapfile:
1571 return changeset_printer(ui, repo, matchfn, opts, buffered)
1571 return changeset_printer(ui, repo, matchfn, opts, buffered)
1572
1572
1573 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1573 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1574
1574
1575 def showmarker(fm, marker, index=None):
1575 def showmarker(fm, marker, index=None):
1576 """utility function to display obsolescence marker in a readable way
1576 """utility function to display obsolescence marker in a readable way
1577
1577
1578 To be used by debug function."""
1578 To be used by debug function."""
1579 if index is not None:
1579 if index is not None:
1580 fm.write('index', '%i ', index)
1580 fm.write('index', '%i ', index)
1581 fm.write('precnode', '%s ', hex(marker.precnode()))
1581 fm.write('precnode', '%s ', hex(marker.precnode()))
1582 succs = marker.succnodes()
1582 succs = marker.succnodes()
1583 fm.condwrite(succs, 'succnodes', '%s ',
1583 fm.condwrite(succs, 'succnodes', '%s ',
1584 fm.formatlist(map(hex, succs), name='node'))
1584 fm.formatlist(map(hex, succs), name='node'))
1585 fm.write('flag', '%X ', marker.flags())
1585 fm.write('flag', '%X ', marker.flags())
1586 parents = marker.parentnodes()
1586 parents = marker.parentnodes()
1587 if parents is not None:
1587 if parents is not None:
1588 fm.write('parentnodes', '{%s} ',
1588 fm.write('parentnodes', '{%s} ',
1589 fm.formatlist(map(hex, parents), name='node', sep=', '))
1589 fm.formatlist(map(hex, parents), name='node', sep=', '))
1590 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1590 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1591 meta = marker.metadata().copy()
1591 meta = marker.metadata().copy()
1592 meta.pop('date', None)
1592 meta.pop('date', None)
1593 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1593 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1594 fm.plain('\n')
1594 fm.plain('\n')
1595
1595
1596 def finddate(ui, repo, date):
1596 def finddate(ui, repo, date):
1597 """Find the tipmost changeset that matches the given date spec"""
1597 """Find the tipmost changeset that matches the given date spec"""
1598
1598
1599 df = util.matchdate(date)
1599 df = util.matchdate(date)
1600 m = scmutil.matchall(repo)
1600 m = scmutil.matchall(repo)
1601 results = {}
1601 results = {}
1602
1602
1603 def prep(ctx, fns):
1603 def prep(ctx, fns):
1604 d = ctx.date()
1604 d = ctx.date()
1605 if df(d[0]):
1605 if df(d[0]):
1606 results[ctx.rev()] = d
1606 results[ctx.rev()] = d
1607
1607
1608 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1608 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1609 rev = ctx.rev()
1609 rev = ctx.rev()
1610 if rev in results:
1610 if rev in results:
1611 ui.status(_("found revision %s from %s\n") %
1611 ui.status(_("found revision %s from %s\n") %
1612 (rev, util.datestr(results[rev])))
1612 (rev, util.datestr(results[rev])))
1613 return '%d' % rev
1613 return '%d' % rev
1614
1614
1615 raise error.Abort(_("revision matching date not found"))
1615 raise error.Abort(_("revision matching date not found"))
1616
1616
1617 def increasingwindows(windowsize=8, sizelimit=512):
1617 def increasingwindows(windowsize=8, sizelimit=512):
1618 while True:
1618 while True:
1619 yield windowsize
1619 yield windowsize
1620 if windowsize < sizelimit:
1620 if windowsize < sizelimit:
1621 windowsize *= 2
1621 windowsize *= 2
1622
1622
1623 class FileWalkError(Exception):
1623 class FileWalkError(Exception):
1624 pass
1624 pass
1625
1625
1626 def walkfilerevs(repo, match, follow, revs, fncache):
1626 def walkfilerevs(repo, match, follow, revs, fncache):
1627 '''Walks the file history for the matched files.
1627 '''Walks the file history for the matched files.
1628
1628
1629 Returns the changeset revs that are involved in the file history.
1629 Returns the changeset revs that are involved in the file history.
1630
1630
1631 Throws FileWalkError if the file history can't be walked using
1631 Throws FileWalkError if the file history can't be walked using
1632 filelogs alone.
1632 filelogs alone.
1633 '''
1633 '''
1634 wanted = set()
1634 wanted = set()
1635 copies = []
1635 copies = []
1636 minrev, maxrev = min(revs), max(revs)
1636 minrev, maxrev = min(revs), max(revs)
1637 def filerevgen(filelog, last):
1637 def filerevgen(filelog, last):
1638 """
1638 """
1639 Only files, no patterns. Check the history of each file.
1639 Only files, no patterns. Check the history of each file.
1640
1640
1641 Examines filelog entries within minrev, maxrev linkrev range
1641 Examines filelog entries within minrev, maxrev linkrev range
1642 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1642 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1643 tuples in backwards order
1643 tuples in backwards order
1644 """
1644 """
1645 cl_count = len(repo)
1645 cl_count = len(repo)
1646 revs = []
1646 revs = []
1647 for j in xrange(0, last + 1):
1647 for j in xrange(0, last + 1):
1648 linkrev = filelog.linkrev(j)
1648 linkrev = filelog.linkrev(j)
1649 if linkrev < minrev:
1649 if linkrev < minrev:
1650 continue
1650 continue
1651 # only yield rev for which we have the changelog, it can
1651 # only yield rev for which we have the changelog, it can
1652 # happen while doing "hg log" during a pull or commit
1652 # happen while doing "hg log" during a pull or commit
1653 if linkrev >= cl_count:
1653 if linkrev >= cl_count:
1654 break
1654 break
1655
1655
1656 parentlinkrevs = []
1656 parentlinkrevs = []
1657 for p in filelog.parentrevs(j):
1657 for p in filelog.parentrevs(j):
1658 if p != nullrev:
1658 if p != nullrev:
1659 parentlinkrevs.append(filelog.linkrev(p))
1659 parentlinkrevs.append(filelog.linkrev(p))
1660 n = filelog.node(j)
1660 n = filelog.node(j)
1661 revs.append((linkrev, parentlinkrevs,
1661 revs.append((linkrev, parentlinkrevs,
1662 follow and filelog.renamed(n)))
1662 follow and filelog.renamed(n)))
1663
1663
1664 return reversed(revs)
1664 return reversed(revs)
1665 def iterfiles():
1665 def iterfiles():
1666 pctx = repo['.']
1666 pctx = repo['.']
1667 for filename in match.files():
1667 for filename in match.files():
1668 if follow:
1668 if follow:
1669 if filename not in pctx:
1669 if filename not in pctx:
1670 raise error.Abort(_('cannot follow file not in parent '
1670 raise error.Abort(_('cannot follow file not in parent '
1671 'revision: "%s"') % filename)
1671 'revision: "%s"') % filename)
1672 yield filename, pctx[filename].filenode()
1672 yield filename, pctx[filename].filenode()
1673 else:
1673 else:
1674 yield filename, None
1674 yield filename, None
1675 for filename_node in copies:
1675 for filename_node in copies:
1676 yield filename_node
1676 yield filename_node
1677
1677
1678 for file_, node in iterfiles():
1678 for file_, node in iterfiles():
1679 filelog = repo.file(file_)
1679 filelog = repo.file(file_)
1680 if not len(filelog):
1680 if not len(filelog):
1681 if node is None:
1681 if node is None:
1682 # A zero count may be a directory or deleted file, so
1682 # A zero count may be a directory or deleted file, so
1683 # try to find matching entries on the slow path.
1683 # try to find matching entries on the slow path.
1684 if follow:
1684 if follow:
1685 raise error.Abort(
1685 raise error.Abort(
1686 _('cannot follow nonexistent file: "%s"') % file_)
1686 _('cannot follow nonexistent file: "%s"') % file_)
1687 raise FileWalkError("Cannot walk via filelog")
1687 raise FileWalkError("Cannot walk via filelog")
1688 else:
1688 else:
1689 continue
1689 continue
1690
1690
1691 if node is None:
1691 if node is None:
1692 last = len(filelog) - 1
1692 last = len(filelog) - 1
1693 else:
1693 else:
1694 last = filelog.rev(node)
1694 last = filelog.rev(node)
1695
1695
1696 # keep track of all ancestors of the file
1696 # keep track of all ancestors of the file
1697 ancestors = {filelog.linkrev(last)}
1697 ancestors = {filelog.linkrev(last)}
1698
1698
1699 # iterate from latest to oldest revision
1699 # iterate from latest to oldest revision
1700 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1700 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1701 if not follow:
1701 if not follow:
1702 if rev > maxrev:
1702 if rev > maxrev:
1703 continue
1703 continue
1704 else:
1704 else:
1705 # Note that last might not be the first interesting
1705 # Note that last might not be the first interesting
1706 # rev to us:
1706 # rev to us:
1707 # if the file has been changed after maxrev, we'll
1707 # if the file has been changed after maxrev, we'll
1708 # have linkrev(last) > maxrev, and we still need
1708 # have linkrev(last) > maxrev, and we still need
1709 # to explore the file graph
1709 # to explore the file graph
1710 if rev not in ancestors:
1710 if rev not in ancestors:
1711 continue
1711 continue
1712 # XXX insert 1327 fix here
1712 # XXX insert 1327 fix here
1713 if flparentlinkrevs:
1713 if flparentlinkrevs:
1714 ancestors.update(flparentlinkrevs)
1714 ancestors.update(flparentlinkrevs)
1715
1715
1716 fncache.setdefault(rev, []).append(file_)
1716 fncache.setdefault(rev, []).append(file_)
1717 wanted.add(rev)
1717 wanted.add(rev)
1718 if copied:
1718 if copied:
1719 copies.append(copied)
1719 copies.append(copied)
1720
1720
1721 return wanted
1721 return wanted
1722
1722
1723 class _followfilter(object):
1723 class _followfilter(object):
1724 def __init__(self, repo, onlyfirst=False):
1724 def __init__(self, repo, onlyfirst=False):
1725 self.repo = repo
1725 self.repo = repo
1726 self.startrev = nullrev
1726 self.startrev = nullrev
1727 self.roots = set()
1727 self.roots = set()
1728 self.onlyfirst = onlyfirst
1728 self.onlyfirst = onlyfirst
1729
1729
1730 def match(self, rev):
1730 def match(self, rev):
1731 def realparents(rev):
1731 def realparents(rev):
1732 if self.onlyfirst:
1732 if self.onlyfirst:
1733 return self.repo.changelog.parentrevs(rev)[0:1]
1733 return self.repo.changelog.parentrevs(rev)[0:1]
1734 else:
1734 else:
1735 return filter(lambda x: x != nullrev,
1735 return filter(lambda x: x != nullrev,
1736 self.repo.changelog.parentrevs(rev))
1736 self.repo.changelog.parentrevs(rev))
1737
1737
1738 if self.startrev == nullrev:
1738 if self.startrev == nullrev:
1739 self.startrev = rev
1739 self.startrev = rev
1740 return True
1740 return True
1741
1741
1742 if rev > self.startrev:
1742 if rev > self.startrev:
1743 # forward: all descendants
1743 # forward: all descendants
1744 if not self.roots:
1744 if not self.roots:
1745 self.roots.add(self.startrev)
1745 self.roots.add(self.startrev)
1746 for parent in realparents(rev):
1746 for parent in realparents(rev):
1747 if parent in self.roots:
1747 if parent in self.roots:
1748 self.roots.add(rev)
1748 self.roots.add(rev)
1749 return True
1749 return True
1750 else:
1750 else:
1751 # backwards: all parents
1751 # backwards: all parents
1752 if not self.roots:
1752 if not self.roots:
1753 self.roots.update(realparents(self.startrev))
1753 self.roots.update(realparents(self.startrev))
1754 if rev in self.roots:
1754 if rev in self.roots:
1755 self.roots.remove(rev)
1755 self.roots.remove(rev)
1756 self.roots.update(realparents(rev))
1756 self.roots.update(realparents(rev))
1757 return True
1757 return True
1758
1758
1759 return False
1759 return False
1760
1760
1761 def walkchangerevs(repo, match, opts, prepare):
1761 def walkchangerevs(repo, match, opts, prepare):
1762 '''Iterate over files and the revs in which they changed.
1762 '''Iterate over files and the revs in which they changed.
1763
1763
1764 Callers most commonly need to iterate backwards over the history
1764 Callers most commonly need to iterate backwards over the history
1765 in which they are interested. Doing so has awful (quadratic-looking)
1765 in which they are interested. Doing so has awful (quadratic-looking)
1766 performance, so we use iterators in a "windowed" way.
1766 performance, so we use iterators in a "windowed" way.
1767
1767
1768 We walk a window of revisions in the desired order. Within the
1768 We walk a window of revisions in the desired order. Within the
1769 window, we first walk forwards to gather data, then in the desired
1769 window, we first walk forwards to gather data, then in the desired
1770 order (usually backwards) to display it.
1770 order (usually backwards) to display it.
1771
1771
1772 This function returns an iterator yielding contexts. Before
1772 This function returns an iterator yielding contexts. Before
1773 yielding each context, the iterator will first call the prepare
1773 yielding each context, the iterator will first call the prepare
1774 function on each context in the window in forward order.'''
1774 function on each context in the window in forward order.'''
1775
1775
1776 follow = opts.get('follow') or opts.get('follow_first')
1776 follow = opts.get('follow') or opts.get('follow_first')
1777 revs = _logrevs(repo, opts)
1777 revs = _logrevs(repo, opts)
1778 if not revs:
1778 if not revs:
1779 return []
1779 return []
1780 wanted = set()
1780 wanted = set()
1781 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1781 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1782 opts.get('removed'))
1782 opts.get('removed'))
1783 fncache = {}
1783 fncache = {}
1784 change = repo.changectx
1784 change = repo.changectx
1785
1785
1786 # First step is to fill wanted, the set of revisions that we want to yield.
1786 # First step is to fill wanted, the set of revisions that we want to yield.
1787 # When it does not induce extra cost, we also fill fncache for revisions in
1787 # When it does not induce extra cost, we also fill fncache for revisions in
1788 # wanted: a cache of filenames that were changed (ctx.files()) and that
1788 # wanted: a cache of filenames that were changed (ctx.files()) and that
1789 # match the file filtering conditions.
1789 # match the file filtering conditions.
1790
1790
1791 if match.always():
1791 if match.always():
1792 # No files, no patterns. Display all revs.
1792 # No files, no patterns. Display all revs.
1793 wanted = revs
1793 wanted = revs
1794 elif not slowpath:
1794 elif not slowpath:
1795 # We only have to read through the filelog to find wanted revisions
1795 # We only have to read through the filelog to find wanted revisions
1796
1796
1797 try:
1797 try:
1798 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1798 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1799 except FileWalkError:
1799 except FileWalkError:
1800 slowpath = True
1800 slowpath = True
1801
1801
1802 # We decided to fall back to the slowpath because at least one
1802 # We decided to fall back to the slowpath because at least one
1803 # of the paths was not a file. Check to see if at least one of them
1803 # of the paths was not a file. Check to see if at least one of them
1804 # existed in history, otherwise simply return
1804 # existed in history, otherwise simply return
1805 for path in match.files():
1805 for path in match.files():
1806 if path == '.' or path in repo.store:
1806 if path == '.' or path in repo.store:
1807 break
1807 break
1808 else:
1808 else:
1809 return []
1809 return []
1810
1810
1811 if slowpath:
1811 if slowpath:
1812 # We have to read the changelog to match filenames against
1812 # We have to read the changelog to match filenames against
1813 # changed files
1813 # changed files
1814
1814
1815 if follow:
1815 if follow:
1816 raise error.Abort(_('can only follow copies/renames for explicit '
1816 raise error.Abort(_('can only follow copies/renames for explicit '
1817 'filenames'))
1817 'filenames'))
1818
1818
1819 # The slow path checks files modified in every changeset.
1819 # The slow path checks files modified in every changeset.
1820 # This is really slow on large repos, so compute the set lazily.
1820 # This is really slow on large repos, so compute the set lazily.
1821 class lazywantedset(object):
1821 class lazywantedset(object):
1822 def __init__(self):
1822 def __init__(self):
1823 self.set = set()
1823 self.set = set()
1824 self.revs = set(revs)
1824 self.revs = set(revs)
1825
1825
1826 # No need to worry about locality here because it will be accessed
1826 # No need to worry about locality here because it will be accessed
1827 # in the same order as the increasing window below.
1827 # in the same order as the increasing window below.
1828 def __contains__(self, value):
1828 def __contains__(self, value):
1829 if value in self.set:
1829 if value in self.set:
1830 return True
1830 return True
1831 elif not value in self.revs:
1831 elif not value in self.revs:
1832 return False
1832 return False
1833 else:
1833 else:
1834 self.revs.discard(value)
1834 self.revs.discard(value)
1835 ctx = change(value)
1835 ctx = change(value)
1836 matches = filter(match, ctx.files())
1836 matches = filter(match, ctx.files())
1837 if matches:
1837 if matches:
1838 fncache[value] = matches
1838 fncache[value] = matches
1839 self.set.add(value)
1839 self.set.add(value)
1840 return True
1840 return True
1841 return False
1841 return False
1842
1842
1843 def discard(self, value):
1843 def discard(self, value):
1844 self.revs.discard(value)
1844 self.revs.discard(value)
1845 self.set.discard(value)
1845 self.set.discard(value)
1846
1846
1847 wanted = lazywantedset()
1847 wanted = lazywantedset()
1848
1848
1849 # it might be worthwhile to do this in the iterator if the rev range
1849 # it might be worthwhile to do this in the iterator if the rev range
1850 # is descending and the prune args are all within that range
1850 # is descending and the prune args are all within that range
1851 for rev in opts.get('prune', ()):
1851 for rev in opts.get('prune', ()):
1852 rev = repo[rev].rev()
1852 rev = repo[rev].rev()
1853 ff = _followfilter(repo)
1853 ff = _followfilter(repo)
1854 stop = min(revs[0], revs[-1])
1854 stop = min(revs[0], revs[-1])
1855 for x in xrange(rev, stop - 1, -1):
1855 for x in xrange(rev, stop - 1, -1):
1856 if ff.match(x):
1856 if ff.match(x):
1857 wanted = wanted - [x]
1857 wanted = wanted - [x]
1858
1858
1859 # Now that wanted is correctly initialized, we can iterate over the
1859 # Now that wanted is correctly initialized, we can iterate over the
1860 # revision range, yielding only revisions in wanted.
1860 # revision range, yielding only revisions in wanted.
1861 def iterate():
1861 def iterate():
1862 if follow and match.always():
1862 if follow and match.always():
1863 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1863 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1864 def want(rev):
1864 def want(rev):
1865 return ff.match(rev) and rev in wanted
1865 return ff.match(rev) and rev in wanted
1866 else:
1866 else:
1867 def want(rev):
1867 def want(rev):
1868 return rev in wanted
1868 return rev in wanted
1869
1869
1870 it = iter(revs)
1870 it = iter(revs)
1871 stopiteration = False
1871 stopiteration = False
1872 for windowsize in increasingwindows():
1872 for windowsize in increasingwindows():
1873 nrevs = []
1873 nrevs = []
1874 for i in xrange(windowsize):
1874 for i in xrange(windowsize):
1875 rev = next(it, None)
1875 rev = next(it, None)
1876 if rev is None:
1876 if rev is None:
1877 stopiteration = True
1877 stopiteration = True
1878 break
1878 break
1879 elif want(rev):
1879 elif want(rev):
1880 nrevs.append(rev)
1880 nrevs.append(rev)
1881 for rev in sorted(nrevs):
1881 for rev in sorted(nrevs):
1882 fns = fncache.get(rev)
1882 fns = fncache.get(rev)
1883 ctx = change(rev)
1883 ctx = change(rev)
1884 if not fns:
1884 if not fns:
1885 def fns_generator():
1885 def fns_generator():
1886 for f in ctx.files():
1886 for f in ctx.files():
1887 if match(f):
1887 if match(f):
1888 yield f
1888 yield f
1889 fns = fns_generator()
1889 fns = fns_generator()
1890 prepare(ctx, fns)
1890 prepare(ctx, fns)
1891 for rev in nrevs:
1891 for rev in nrevs:
1892 yield change(rev)
1892 yield change(rev)
1893
1893
1894 if stopiteration:
1894 if stopiteration:
1895 break
1895 break
1896
1896
1897 return iterate()
1897 return iterate()
1898
1898
1899 def _makefollowlogfilematcher(repo, files, followfirst):
1899 def _makefollowlogfilematcher(repo, files, followfirst):
1900 # When displaying a revision with --patch --follow FILE, we have
1900 # When displaying a revision with --patch --follow FILE, we have
1901 # to know which file of the revision must be diffed. With
1901 # to know which file of the revision must be diffed. With
1902 # --follow, we want the names of the ancestors of FILE in the
1902 # --follow, we want the names of the ancestors of FILE in the
1903 # revision, stored in "fcache". "fcache" is populated by
1903 # revision, stored in "fcache". "fcache" is populated by
1904 # reproducing the graph traversal already done by --follow revset
1904 # reproducing the graph traversal already done by --follow revset
1905 # and relating revs to file names (which is not "correct" but
1905 # and relating revs to file names (which is not "correct" but
1906 # good enough).
1906 # good enough).
1907 fcache = {}
1907 fcache = {}
1908 fcacheready = [False]
1908 fcacheready = [False]
1909 pctx = repo['.']
1909 pctx = repo['.']
1910
1910
1911 def populate():
1911 def populate():
1912 for fn in files:
1912 for fn in files:
1913 fctx = pctx[fn]
1913 fctx = pctx[fn]
1914 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1914 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1915 for c in fctx.ancestors(followfirst=followfirst):
1915 for c in fctx.ancestors(followfirst=followfirst):
1916 fcache.setdefault(c.rev(), set()).add(c.path())
1916 fcache.setdefault(c.rev(), set()).add(c.path())
1917
1917
1918 def filematcher(rev):
1918 def filematcher(rev):
1919 if not fcacheready[0]:
1919 if not fcacheready[0]:
1920 # Lazy initialization
1920 # Lazy initialization
1921 fcacheready[0] = True
1921 fcacheready[0] = True
1922 populate()
1922 populate()
1923 return scmutil.matchfiles(repo, fcache.get(rev, []))
1923 return scmutil.matchfiles(repo, fcache.get(rev, []))
1924
1924
1925 return filematcher
1925 return filematcher
1926
1926
1927 def _makenofollowlogfilematcher(repo, pats, opts):
1927 def _makenofollowlogfilematcher(repo, pats, opts):
1928 '''hook for extensions to override the filematcher for non-follow cases'''
1928 '''hook for extensions to override the filematcher for non-follow cases'''
1929 return None
1929 return None
1930
1930
1931 def _makelogrevset(repo, pats, opts, revs):
1931 def _makelogrevset(repo, pats, opts, revs):
1932 """Return (expr, filematcher) where expr is a revset string built
1932 """Return (expr, filematcher) where expr is a revset string built
1933 from log options and file patterns or None. If --stat or --patch
1933 from log options and file patterns or None. If --stat or --patch
1934 are not passed filematcher is None. Otherwise it is a callable
1934 are not passed filematcher is None. Otherwise it is a callable
1935 taking a revision number and returning a match objects filtering
1935 taking a revision number and returning a match objects filtering
1936 the files to be detailed when displaying the revision.
1936 the files to be detailed when displaying the revision.
1937 """
1937 """
1938 opt2revset = {
1938 opt2revset = {
1939 'no_merges': ('not merge()', None),
1939 'no_merges': ('not merge()', None),
1940 'only_merges': ('merge()', None),
1940 'only_merges': ('merge()', None),
1941 '_ancestors': ('ancestors(%(val)s)', None),
1941 '_ancestors': ('ancestors(%(val)s)', None),
1942 '_fancestors': ('_firstancestors(%(val)s)', None),
1942 '_fancestors': ('_firstancestors(%(val)s)', None),
1943 '_descendants': ('descendants(%(val)s)', None),
1943 '_descendants': ('descendants(%(val)s)', None),
1944 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1944 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1945 '_matchfiles': ('_matchfiles(%(val)s)', None),
1945 '_matchfiles': ('_matchfiles(%(val)s)', None),
1946 'date': ('date(%(val)r)', None),
1946 'date': ('date(%(val)r)', None),
1947 'branch': ('branch(%(val)r)', ' or '),
1947 'branch': ('branch(%(val)r)', ' or '),
1948 '_patslog': ('filelog(%(val)r)', ' or '),
1948 '_patslog': ('filelog(%(val)r)', ' or '),
1949 '_patsfollow': ('follow(%(val)r)', ' or '),
1949 '_patsfollow': ('follow(%(val)r)', ' or '),
1950 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1950 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1951 'keyword': ('keyword(%(val)r)', ' or '),
1951 'keyword': ('keyword(%(val)r)', ' or '),
1952 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1952 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1953 'user': ('user(%(val)r)', ' or '),
1953 'user': ('user(%(val)r)', ' or '),
1954 }
1954 }
1955
1955
1956 opts = dict(opts)
1956 opts = dict(opts)
1957 # follow or not follow?
1957 # follow or not follow?
1958 follow = opts.get('follow') or opts.get('follow_first')
1958 follow = opts.get('follow') or opts.get('follow_first')
1959 if opts.get('follow_first'):
1959 if opts.get('follow_first'):
1960 followfirst = 1
1960 followfirst = 1
1961 else:
1961 else:
1962 followfirst = 0
1962 followfirst = 0
1963 # --follow with FILE behavior depends on revs...
1963 # --follow with FILE behavior depends on revs...
1964 it = iter(revs)
1964 it = iter(revs)
1965 startrev = next(it)
1965 startrev = next(it)
1966 followdescendants = startrev < next(it, startrev)
1966 followdescendants = startrev < next(it, startrev)
1967
1967
1968 # branch and only_branch are really aliases and must be handled at
1968 # branch and only_branch are really aliases and must be handled at
1969 # the same time
1969 # the same time
1970 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1970 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1971 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1971 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1972 # pats/include/exclude are passed to match.match() directly in
1972 # pats/include/exclude are passed to match.match() directly in
1973 # _matchfiles() revset but walkchangerevs() builds its matcher with
1973 # _matchfiles() revset but walkchangerevs() builds its matcher with
1974 # scmutil.match(). The difference is input pats are globbed on
1974 # scmutil.match(). The difference is input pats are globbed on
1975 # platforms without shell expansion (windows).
1975 # platforms without shell expansion (windows).
1976 wctx = repo[None]
1976 wctx = repo[None]
1977 match, pats = scmutil.matchandpats(wctx, pats, opts)
1977 match, pats = scmutil.matchandpats(wctx, pats, opts)
1978 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1978 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1979 opts.get('removed'))
1979 opts.get('removed'))
1980 if not slowpath:
1980 if not slowpath:
1981 for f in match.files():
1981 for f in match.files():
1982 if follow and f not in wctx:
1982 if follow and f not in wctx:
1983 # If the file exists, it may be a directory, so let it
1983 # If the file exists, it may be a directory, so let it
1984 # take the slow path.
1984 # take the slow path.
1985 if os.path.exists(repo.wjoin(f)):
1985 if os.path.exists(repo.wjoin(f)):
1986 slowpath = True
1986 slowpath = True
1987 continue
1987 continue
1988 else:
1988 else:
1989 raise error.Abort(_('cannot follow file not in parent '
1989 raise error.Abort(_('cannot follow file not in parent '
1990 'revision: "%s"') % f)
1990 'revision: "%s"') % f)
1991 filelog = repo.file(f)
1991 filelog = repo.file(f)
1992 if not filelog:
1992 if not filelog:
1993 # A zero count may be a directory or deleted file, so
1993 # A zero count may be a directory or deleted file, so
1994 # try to find matching entries on the slow path.
1994 # try to find matching entries on the slow path.
1995 if follow:
1995 if follow:
1996 raise error.Abort(
1996 raise error.Abort(
1997 _('cannot follow nonexistent file: "%s"') % f)
1997 _('cannot follow nonexistent file: "%s"') % f)
1998 slowpath = True
1998 slowpath = True
1999
1999
2000 # We decided to fall back to the slowpath because at least one
2000 # We decided to fall back to the slowpath because at least one
2001 # of the paths was not a file. Check to see if at least one of them
2001 # of the paths was not a file. Check to see if at least one of them
2002 # existed in history - in that case, we'll continue down the
2002 # existed in history - in that case, we'll continue down the
2003 # slowpath; otherwise, we can turn off the slowpath
2003 # slowpath; otherwise, we can turn off the slowpath
2004 if slowpath:
2004 if slowpath:
2005 for path in match.files():
2005 for path in match.files():
2006 if path == '.' or path in repo.store:
2006 if path == '.' or path in repo.store:
2007 break
2007 break
2008 else:
2008 else:
2009 slowpath = False
2009 slowpath = False
2010
2010
2011 fpats = ('_patsfollow', '_patsfollowfirst')
2011 fpats = ('_patsfollow', '_patsfollowfirst')
2012 fnopats = (('_ancestors', '_fancestors'),
2012 fnopats = (('_ancestors', '_fancestors'),
2013 ('_descendants', '_fdescendants'))
2013 ('_descendants', '_fdescendants'))
2014 if slowpath:
2014 if slowpath:
2015 # See walkchangerevs() slow path.
2015 # See walkchangerevs() slow path.
2016 #
2016 #
2017 # pats/include/exclude cannot be represented as separate
2017 # pats/include/exclude cannot be represented as separate
2018 # revset expressions as their filtering logic applies at file
2018 # revset expressions as their filtering logic applies at file
2019 # level. For instance "-I a -X a" matches a revision touching
2019 # level. For instance "-I a -X a" matches a revision touching
2020 # "a" and "b" while "file(a) and not file(b)" does
2020 # "a" and "b" while "file(a) and not file(b)" does
2021 # not. Besides, filesets are evaluated against the working
2021 # not. Besides, filesets are evaluated against the working
2022 # directory.
2022 # directory.
2023 matchargs = ['r:', 'd:relpath']
2023 matchargs = ['r:', 'd:relpath']
2024 for p in pats:
2024 for p in pats:
2025 matchargs.append('p:' + p)
2025 matchargs.append('p:' + p)
2026 for p in opts.get('include', []):
2026 for p in opts.get('include', []):
2027 matchargs.append('i:' + p)
2027 matchargs.append('i:' + p)
2028 for p in opts.get('exclude', []):
2028 for p in opts.get('exclude', []):
2029 matchargs.append('x:' + p)
2029 matchargs.append('x:' + p)
2030 matchargs = ','.join(('%r' % p) for p in matchargs)
2030 matchargs = ','.join(('%r' % p) for p in matchargs)
2031 opts['_matchfiles'] = matchargs
2031 opts['_matchfiles'] = matchargs
2032 if follow:
2032 if follow:
2033 opts[fnopats[0][followfirst]] = '.'
2033 opts[fnopats[0][followfirst]] = '.'
2034 else:
2034 else:
2035 if follow:
2035 if follow:
2036 if pats:
2036 if pats:
2037 # follow() revset interprets its file argument as a
2037 # follow() revset interprets its file argument as a
2038 # manifest entry, so use match.files(), not pats.
2038 # manifest entry, so use match.files(), not pats.
2039 opts[fpats[followfirst]] = list(match.files())
2039 opts[fpats[followfirst]] = list(match.files())
2040 else:
2040 else:
2041 op = fnopats[followdescendants][followfirst]
2041 op = fnopats[followdescendants][followfirst]
2042 opts[op] = 'rev(%d)' % startrev
2042 opts[op] = 'rev(%d)' % startrev
2043 else:
2043 else:
2044 opts['_patslog'] = list(pats)
2044 opts['_patslog'] = list(pats)
2045
2045
2046 filematcher = None
2046 filematcher = None
2047 if opts.get('patch') or opts.get('stat'):
2047 if opts.get('patch') or opts.get('stat'):
2048 # When following files, track renames via a special matcher.
2048 # When following files, track renames via a special matcher.
2049 # If we're forced to take the slowpath it means we're following
2049 # If we're forced to take the slowpath it means we're following
2050 # at least one pattern/directory, so don't bother with rename tracking.
2050 # at least one pattern/directory, so don't bother with rename tracking.
2051 if follow and not match.always() and not slowpath:
2051 if follow and not match.always() and not slowpath:
2052 # _makefollowlogfilematcher expects its files argument to be
2052 # _makefollowlogfilematcher expects its files argument to be
2053 # relative to the repo root, so use match.files(), not pats.
2053 # relative to the repo root, so use match.files(), not pats.
2054 filematcher = _makefollowlogfilematcher(repo, match.files(),
2054 filematcher = _makefollowlogfilematcher(repo, match.files(),
2055 followfirst)
2055 followfirst)
2056 else:
2056 else:
2057 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2057 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2058 if filematcher is None:
2058 if filematcher is None:
2059 filematcher = lambda rev: match
2059 filematcher = lambda rev: match
2060
2060
2061 expr = []
2061 expr = []
2062 for op, val in sorted(opts.iteritems()):
2062 for op, val in sorted(opts.iteritems()):
2063 if not val:
2063 if not val:
2064 continue
2064 continue
2065 if op not in opt2revset:
2065 if op not in opt2revset:
2066 continue
2066 continue
2067 revop, andor = opt2revset[op]
2067 revop, andor = opt2revset[op]
2068 if '%(val)' not in revop:
2068 if '%(val)' not in revop:
2069 expr.append(revop)
2069 expr.append(revop)
2070 else:
2070 else:
2071 if not isinstance(val, list):
2071 if not isinstance(val, list):
2072 e = revop % {'val': val}
2072 e = revop % {'val': val}
2073 else:
2073 else:
2074 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2074 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2075 expr.append(e)
2075 expr.append(e)
2076
2076
2077 if expr:
2077 if expr:
2078 expr = '(' + ' and '.join(expr) + ')'
2078 expr = '(' + ' and '.join(expr) + ')'
2079 else:
2079 else:
2080 expr = None
2080 expr = None
2081 return expr, filematcher
2081 return expr, filematcher
2082
2082
2083 def _logrevs(repo, opts):
2083 def _logrevs(repo, opts):
2084 # Default --rev value depends on --follow but --follow behavior
2084 # Default --rev value depends on --follow but --follow behavior
2085 # depends on revisions resolved from --rev...
2085 # depends on revisions resolved from --rev...
2086 follow = opts.get('follow') or opts.get('follow_first')
2086 follow = opts.get('follow') or opts.get('follow_first')
2087 if opts.get('rev'):
2087 if opts.get('rev'):
2088 revs = scmutil.revrange(repo, opts['rev'])
2088 revs = scmutil.revrange(repo, opts['rev'])
2089 elif follow and repo.dirstate.p1() == nullid:
2089 elif follow and repo.dirstate.p1() == nullid:
2090 revs = smartset.baseset()
2090 revs = smartset.baseset()
2091 elif follow:
2091 elif follow:
2092 revs = repo.revs('reverse(:.)')
2092 revs = repo.revs('reverse(:.)')
2093 else:
2093 else:
2094 revs = smartset.spanset(repo)
2094 revs = smartset.spanset(repo)
2095 revs.reverse()
2095 revs.reverse()
2096 return revs
2096 return revs
2097
2097
2098 def getgraphlogrevs(repo, pats, opts):
2098 def getgraphlogrevs(repo, pats, opts):
2099 """Return (revs, expr, filematcher) where revs is an iterable of
2099 """Return (revs, expr, filematcher) where revs is an iterable of
2100 revision numbers, expr is a revset string built from log options
2100 revision numbers, expr is a revset string built from log options
2101 and file patterns or None, and used to filter 'revs'. If --stat or
2101 and file patterns or None, and used to filter 'revs'. If --stat or
2102 --patch are not passed filematcher is None. Otherwise it is a
2102 --patch are not passed filematcher is None. Otherwise it is a
2103 callable taking a revision number and returning a match objects
2103 callable taking a revision number and returning a match objects
2104 filtering the files to be detailed when displaying the revision.
2104 filtering the files to be detailed when displaying the revision.
2105 """
2105 """
2106 limit = loglimit(opts)
2106 limit = loglimit(opts)
2107 revs = _logrevs(repo, opts)
2107 revs = _logrevs(repo, opts)
2108 if not revs:
2108 if not revs:
2109 return smartset.baseset(), None, None
2109 return smartset.baseset(), None, None
2110 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2110 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2111 if opts.get('rev'):
2111 if opts.get('rev'):
2112 # User-specified revs might be unsorted, but don't sort before
2112 # User-specified revs might be unsorted, but don't sort before
2113 # _makelogrevset because it might depend on the order of revs
2113 # _makelogrevset because it might depend on the order of revs
2114 if not (revs.isdescending() or revs.istopo()):
2114 if not (revs.isdescending() or revs.istopo()):
2115 revs.sort(reverse=True)
2115 revs.sort(reverse=True)
2116 if expr:
2116 if expr:
2117 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2117 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2118 revs = matcher(repo, revs)
2118 revs = matcher(repo, revs)
2119 if limit is not None:
2119 if limit is not None:
2120 limitedrevs = []
2120 limitedrevs = []
2121 for idx, rev in enumerate(revs):
2121 for idx, rev in enumerate(revs):
2122 if idx >= limit:
2122 if idx >= limit:
2123 break
2123 break
2124 limitedrevs.append(rev)
2124 limitedrevs.append(rev)
2125 revs = smartset.baseset(limitedrevs)
2125 revs = smartset.baseset(limitedrevs)
2126
2126
2127 return revs, expr, filematcher
2127 return revs, expr, filematcher
2128
2128
2129 def getlogrevs(repo, pats, opts):
2129 def getlogrevs(repo, pats, opts):
2130 """Return (revs, expr, filematcher) where revs is an iterable of
2130 """Return (revs, expr, filematcher) where revs is an iterable of
2131 revision numbers, expr is a revset string built from log options
2131 revision numbers, expr is a revset string built from log options
2132 and file patterns or None, and used to filter 'revs'. If --stat or
2132 and file patterns or None, and used to filter 'revs'. If --stat or
2133 --patch are not passed filematcher is None. Otherwise it is a
2133 --patch are not passed filematcher is None. Otherwise it is a
2134 callable taking a revision number and returning a match objects
2134 callable taking a revision number and returning a match objects
2135 filtering the files to be detailed when displaying the revision.
2135 filtering the files to be detailed when displaying the revision.
2136 """
2136 """
2137 limit = loglimit(opts)
2137 limit = loglimit(opts)
2138 revs = _logrevs(repo, opts)
2138 revs = _logrevs(repo, opts)
2139 if not revs:
2139 if not revs:
2140 return smartset.baseset([]), None, None
2140 return smartset.baseset([]), None, None
2141 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2141 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2142 if expr:
2142 if expr:
2143 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2143 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2144 revs = matcher(repo, revs)
2144 revs = matcher(repo, revs)
2145 if limit is not None:
2145 if limit is not None:
2146 limitedrevs = []
2146 limitedrevs = []
2147 for idx, r in enumerate(revs):
2147 for idx, r in enumerate(revs):
2148 if limit <= idx:
2148 if limit <= idx:
2149 break
2149 break
2150 limitedrevs.append(r)
2150 limitedrevs.append(r)
2151 revs = smartset.baseset(limitedrevs)
2151 revs = smartset.baseset(limitedrevs)
2152
2152
2153 return revs, expr, filematcher
2153 return revs, expr, filematcher
2154
2154
2155 def _graphnodeformatter(ui, displayer):
2155 def _graphnodeformatter(ui, displayer):
2156 spec = ui.config('ui', 'graphnodetemplate')
2156 spec = ui.config('ui', 'graphnodetemplate')
2157 if not spec:
2157 if not spec:
2158 return templatekw.showgraphnode # fast path for "{graphnode}"
2158 return templatekw.showgraphnode # fast path for "{graphnode}"
2159
2159
2160 spec = templater.unquotestring(spec)
2160 spec = templater.unquotestring(spec)
2161 templ = formatter.gettemplater(ui, 'graphnode', spec)
2161 templ = formatter.gettemplater(ui, 'graphnode', spec)
2162 cache = {}
2162 cache = {}
2163 if isinstance(displayer, changeset_templater):
2163 if isinstance(displayer, changeset_templater):
2164 cache = displayer.cache # reuse cache of slow templates
2164 cache = displayer.cache # reuse cache of slow templates
2165 props = templatekw.keywords.copy()
2165 props = templatekw.keywords.copy()
2166 props['templ'] = templ
2166 props['templ'] = templ
2167 props['cache'] = cache
2167 props['cache'] = cache
2168 def formatnode(repo, ctx):
2168 def formatnode(repo, ctx):
2169 props['ctx'] = ctx
2169 props['ctx'] = ctx
2170 props['repo'] = repo
2170 props['repo'] = repo
2171 props['ui'] = repo.ui
2171 props['ui'] = repo.ui
2172 props['revcache'] = {}
2172 props['revcache'] = {}
2173 return templater.stringify(templ('graphnode', **props))
2173 return templater.stringify(templ('graphnode', **props))
2174 return formatnode
2174 return formatnode
2175
2175
2176 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2176 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2177 filematcher=None):
2177 filematcher=None):
2178 formatnode = _graphnodeformatter(ui, displayer)
2178 formatnode = _graphnodeformatter(ui, displayer)
2179 state = graphmod.asciistate()
2179 state = graphmod.asciistate()
2180 styles = state['styles']
2180 styles = state['styles']
2181
2181
2182 # only set graph styling if HGPLAIN is not set.
2182 # only set graph styling if HGPLAIN is not set.
2183 if ui.plain('graph'):
2183 if ui.plain('graph'):
2184 # set all edge styles to |, the default pre-3.8 behaviour
2184 # set all edge styles to |, the default pre-3.8 behaviour
2185 styles.update(dict.fromkeys(styles, '|'))
2185 styles.update(dict.fromkeys(styles, '|'))
2186 else:
2186 else:
2187 edgetypes = {
2187 edgetypes = {
2188 'parent': graphmod.PARENT,
2188 'parent': graphmod.PARENT,
2189 'grandparent': graphmod.GRANDPARENT,
2189 'grandparent': graphmod.GRANDPARENT,
2190 'missing': graphmod.MISSINGPARENT
2190 'missing': graphmod.MISSINGPARENT
2191 }
2191 }
2192 for name, key in edgetypes.items():
2192 for name, key in edgetypes.items():
2193 # experimental config: experimental.graphstyle.*
2193 # experimental config: experimental.graphstyle.*
2194 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2194 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2195 styles[key])
2195 styles[key])
2196 if not styles[key]:
2196 if not styles[key]:
2197 styles[key] = None
2197 styles[key] = None
2198
2198
2199 # experimental config: experimental.graphshorten
2199 # experimental config: experimental.graphshorten
2200 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2200 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2201
2201
2202 for rev, type, ctx, parents in dag:
2202 for rev, type, ctx, parents in dag:
2203 char = formatnode(repo, ctx)
2203 char = formatnode(repo, ctx)
2204 copies = None
2204 copies = None
2205 if getrenamed and ctx.rev():
2205 if getrenamed and ctx.rev():
2206 copies = []
2206 copies = []
2207 for fn in ctx.files():
2207 for fn in ctx.files():
2208 rename = getrenamed(fn, ctx.rev())
2208 rename = getrenamed(fn, ctx.rev())
2209 if rename:
2209 if rename:
2210 copies.append((fn, rename[0]))
2210 copies.append((fn, rename[0]))
2211 revmatchfn = None
2211 revmatchfn = None
2212 if filematcher is not None:
2212 if filematcher is not None:
2213 revmatchfn = filematcher(ctx.rev())
2213 revmatchfn = filematcher(ctx.rev())
2214 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2214 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2215 lines = displayer.hunk.pop(rev).split('\n')
2215 lines = displayer.hunk.pop(rev).split('\n')
2216 if not lines[-1]:
2216 if not lines[-1]:
2217 del lines[-1]
2217 del lines[-1]
2218 displayer.flush(ctx)
2218 displayer.flush(ctx)
2219 edges = edgefn(type, char, lines, state, rev, parents)
2219 edges = edgefn(type, char, lines, state, rev, parents)
2220 for type, char, lines, coldata in edges:
2220 for type, char, lines, coldata in edges:
2221 graphmod.ascii(ui, state, type, char, lines, coldata)
2221 graphmod.ascii(ui, state, type, char, lines, coldata)
2222 displayer.close()
2222 displayer.close()
2223
2223
2224 def graphlog(ui, repo, pats, opts):
2224 def graphlog(ui, repo, pats, opts):
2225 # Parameters are identical to log command ones
2225 # Parameters are identical to log command ones
2226 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2226 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2227 revdag = graphmod.dagwalker(repo, revs)
2227 revdag = graphmod.dagwalker(repo, revs)
2228
2228
2229 getrenamed = None
2229 getrenamed = None
2230 if opts.get('copies'):
2230 if opts.get('copies'):
2231 endrev = None
2231 endrev = None
2232 if opts.get('rev'):
2232 if opts.get('rev'):
2233 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2233 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2234 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2234 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2235
2235
2236 ui.pager('log')
2236 ui.pager('log')
2237 displayer = show_changeset(ui, repo, opts, buffered=True)
2237 displayer = show_changeset(ui, repo, opts, buffered=True)
2238 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2238 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2239 filematcher)
2239 filematcher)
2240
2240
2241 def checkunsupportedgraphflags(pats, opts):
2241 def checkunsupportedgraphflags(pats, opts):
2242 for op in ["newest_first"]:
2242 for op in ["newest_first"]:
2243 if op in opts and opts[op]:
2243 if op in opts and opts[op]:
2244 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2244 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2245 % op.replace("_", "-"))
2245 % op.replace("_", "-"))
2246
2246
2247 def graphrevs(repo, nodes, opts):
2247 def graphrevs(repo, nodes, opts):
2248 limit = loglimit(opts)
2248 limit = loglimit(opts)
2249 nodes.reverse()
2249 nodes.reverse()
2250 if limit is not None:
2250 if limit is not None:
2251 nodes = nodes[:limit]
2251 nodes = nodes[:limit]
2252 return graphmod.nodes(repo, nodes)
2252 return graphmod.nodes(repo, nodes)
2253
2253
2254 def add(ui, repo, match, prefix, explicitonly, **opts):
2254 def add(ui, repo, match, prefix, explicitonly, **opts):
2255 join = lambda f: os.path.join(prefix, f)
2255 join = lambda f: os.path.join(prefix, f)
2256 bad = []
2256 bad = []
2257
2257
2258 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2258 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2259 names = []
2259 names = []
2260 wctx = repo[None]
2260 wctx = repo[None]
2261 cca = None
2261 cca = None
2262 abort, warn = scmutil.checkportabilityalert(ui)
2262 abort, warn = scmutil.checkportabilityalert(ui)
2263 if abort or warn:
2263 if abort or warn:
2264 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2264 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2265
2265
2266 badmatch = matchmod.badmatch(match, badfn)
2266 badmatch = matchmod.badmatch(match, badfn)
2267 dirstate = repo.dirstate
2267 dirstate = repo.dirstate
2268 # We don't want to just call wctx.walk here, since it would return a lot of
2268 # We don't want to just call wctx.walk here, since it would return a lot of
2269 # clean files, which we aren't interested in and takes time.
2269 # clean files, which we aren't interested in and takes time.
2270 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2270 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2271 True, False, full=False)):
2271 True, False, full=False)):
2272 exact = match.exact(f)
2272 exact = match.exact(f)
2273 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2273 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2274 if cca:
2274 if cca:
2275 cca(f)
2275 cca(f)
2276 names.append(f)
2276 names.append(f)
2277 if ui.verbose or not exact:
2277 if ui.verbose or not exact:
2278 ui.status(_('adding %s\n') % match.rel(f))
2278 ui.status(_('adding %s\n') % match.rel(f))
2279
2279
2280 for subpath in sorted(wctx.substate):
2280 for subpath in sorted(wctx.substate):
2281 sub = wctx.sub(subpath)
2281 sub = wctx.sub(subpath)
2282 try:
2282 try:
2283 submatch = matchmod.subdirmatcher(subpath, match)
2283 submatch = matchmod.subdirmatcher(subpath, match)
2284 if opts.get(r'subrepos'):
2284 if opts.get(r'subrepos'):
2285 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2285 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2286 else:
2286 else:
2287 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2287 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2288 except error.LookupError:
2288 except error.LookupError:
2289 ui.status(_("skipping missing subrepository: %s\n")
2289 ui.status(_("skipping missing subrepository: %s\n")
2290 % join(subpath))
2290 % join(subpath))
2291
2291
2292 if not opts.get(r'dry_run'):
2292 if not opts.get(r'dry_run'):
2293 rejected = wctx.add(names, prefix)
2293 rejected = wctx.add(names, prefix)
2294 bad.extend(f for f in rejected if f in match.files())
2294 bad.extend(f for f in rejected if f in match.files())
2295 return bad
2295 return bad
2296
2296
2297 def addwebdirpath(repo, serverpath, webconf):
2297 def addwebdirpath(repo, serverpath, webconf):
2298 webconf[serverpath] = repo.root
2298 webconf[serverpath] = repo.root
2299 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2299 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2300
2300
2301 for r in repo.revs('filelog("path:.hgsub")'):
2301 for r in repo.revs('filelog("path:.hgsub")'):
2302 ctx = repo[r]
2302 ctx = repo[r]
2303 for subpath in ctx.substate:
2303 for subpath in ctx.substate:
2304 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2304 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2305
2305
2306 def forget(ui, repo, match, prefix, explicitonly):
2306 def forget(ui, repo, match, prefix, explicitonly):
2307 join = lambda f: os.path.join(prefix, f)
2307 join = lambda f: os.path.join(prefix, f)
2308 bad = []
2308 bad = []
2309 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2309 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2310 wctx = repo[None]
2310 wctx = repo[None]
2311 forgot = []
2311 forgot = []
2312
2312
2313 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2313 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2314 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2314 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2315 if explicitonly:
2315 if explicitonly:
2316 forget = [f for f in forget if match.exact(f)]
2316 forget = [f for f in forget if match.exact(f)]
2317
2317
2318 for subpath in sorted(wctx.substate):
2318 for subpath in sorted(wctx.substate):
2319 sub = wctx.sub(subpath)
2319 sub = wctx.sub(subpath)
2320 try:
2320 try:
2321 submatch = matchmod.subdirmatcher(subpath, match)
2321 submatch = matchmod.subdirmatcher(subpath, match)
2322 subbad, subforgot = sub.forget(submatch, prefix)
2322 subbad, subforgot = sub.forget(submatch, prefix)
2323 bad.extend([subpath + '/' + f for f in subbad])
2323 bad.extend([subpath + '/' + f for f in subbad])
2324 forgot.extend([subpath + '/' + f for f in subforgot])
2324 forgot.extend([subpath + '/' + f for f in subforgot])
2325 except error.LookupError:
2325 except error.LookupError:
2326 ui.status(_("skipping missing subrepository: %s\n")
2326 ui.status(_("skipping missing subrepository: %s\n")
2327 % join(subpath))
2327 % join(subpath))
2328
2328
2329 if not explicitonly:
2329 if not explicitonly:
2330 for f in match.files():
2330 for f in match.files():
2331 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2331 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2332 if f not in forgot:
2332 if f not in forgot:
2333 if repo.wvfs.exists(f):
2333 if repo.wvfs.exists(f):
2334 # Don't complain if the exact case match wasn't given.
2334 # Don't complain if the exact case match wasn't given.
2335 # But don't do this until after checking 'forgot', so
2335 # But don't do this until after checking 'forgot', so
2336 # that subrepo files aren't normalized, and this op is
2336 # that subrepo files aren't normalized, and this op is
2337 # purely from data cached by the status walk above.
2337 # purely from data cached by the status walk above.
2338 if repo.dirstate.normalize(f) in repo.dirstate:
2338 if repo.dirstate.normalize(f) in repo.dirstate:
2339 continue
2339 continue
2340 ui.warn(_('not removing %s: '
2340 ui.warn(_('not removing %s: '
2341 'file is already untracked\n')
2341 'file is already untracked\n')
2342 % match.rel(f))
2342 % match.rel(f))
2343 bad.append(f)
2343 bad.append(f)
2344
2344
2345 for f in forget:
2345 for f in forget:
2346 if ui.verbose or not match.exact(f):
2346 if ui.verbose or not match.exact(f):
2347 ui.status(_('removing %s\n') % match.rel(f))
2347 ui.status(_('removing %s\n') % match.rel(f))
2348
2348
2349 rejected = wctx.forget(forget, prefix)
2349 rejected = wctx.forget(forget, prefix)
2350 bad.extend(f for f in rejected if f in match.files())
2350 bad.extend(f for f in rejected if f in match.files())
2351 forgot.extend(f for f in forget if f not in rejected)
2351 forgot.extend(f for f in forget if f not in rejected)
2352 return bad, forgot
2352 return bad, forgot
2353
2353
2354 def files(ui, ctx, m, fm, fmt, subrepos):
2354 def files(ui, ctx, m, fm, fmt, subrepos):
2355 rev = ctx.rev()
2355 rev = ctx.rev()
2356 ret = 1
2356 ret = 1
2357 ds = ctx.repo().dirstate
2357 ds = ctx.repo().dirstate
2358
2358
2359 for f in ctx.matches(m):
2359 for f in ctx.matches(m):
2360 if rev is None and ds[f] == 'r':
2360 if rev is None and ds[f] == 'r':
2361 continue
2361 continue
2362 fm.startitem()
2362 fm.startitem()
2363 if ui.verbose:
2363 if ui.verbose:
2364 fc = ctx[f]
2364 fc = ctx[f]
2365 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2365 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2366 fm.data(abspath=f)
2366 fm.data(abspath=f)
2367 fm.write('path', fmt, m.rel(f))
2367 fm.write('path', fmt, m.rel(f))
2368 ret = 0
2368 ret = 0
2369
2369
2370 for subpath in sorted(ctx.substate):
2370 for subpath in sorted(ctx.substate):
2371 submatch = matchmod.subdirmatcher(subpath, m)
2371 submatch = matchmod.subdirmatcher(subpath, m)
2372 if (subrepos or m.exact(subpath) or any(submatch.files())):
2372 if (subrepos or m.exact(subpath) or any(submatch.files())):
2373 sub = ctx.sub(subpath)
2373 sub = ctx.sub(subpath)
2374 try:
2374 try:
2375 recurse = m.exact(subpath) or subrepos
2375 recurse = m.exact(subpath) or subrepos
2376 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2376 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2377 ret = 0
2377 ret = 0
2378 except error.LookupError:
2378 except error.LookupError:
2379 ui.status(_("skipping missing subrepository: %s\n")
2379 ui.status(_("skipping missing subrepository: %s\n")
2380 % m.abs(subpath))
2380 % m.abs(subpath))
2381
2381
2382 return ret
2382 return ret
2383
2383
2384 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2384 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2385 join = lambda f: os.path.join(prefix, f)
2385 join = lambda f: os.path.join(prefix, f)
2386 ret = 0
2386 ret = 0
2387 s = repo.status(match=m, clean=True)
2387 s = repo.status(match=m, clean=True)
2388 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2388 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2389
2389
2390 wctx = repo[None]
2390 wctx = repo[None]
2391
2391
2392 if warnings is None:
2392 if warnings is None:
2393 warnings = []
2393 warnings = []
2394 warn = True
2394 warn = True
2395 else:
2395 else:
2396 warn = False
2396 warn = False
2397
2397
2398 subs = sorted(wctx.substate)
2398 subs = sorted(wctx.substate)
2399 total = len(subs)
2399 total = len(subs)
2400 count = 0
2400 count = 0
2401 for subpath in subs:
2401 for subpath in subs:
2402 count += 1
2402 count += 1
2403 submatch = matchmod.subdirmatcher(subpath, m)
2403 submatch = matchmod.subdirmatcher(subpath, m)
2404 if subrepos or m.exact(subpath) or any(submatch.files()):
2404 if subrepos or m.exact(subpath) or any(submatch.files()):
2405 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2405 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2406 sub = wctx.sub(subpath)
2406 sub = wctx.sub(subpath)
2407 try:
2407 try:
2408 if sub.removefiles(submatch, prefix, after, force, subrepos,
2408 if sub.removefiles(submatch, prefix, after, force, subrepos,
2409 warnings):
2409 warnings):
2410 ret = 1
2410 ret = 1
2411 except error.LookupError:
2411 except error.LookupError:
2412 warnings.append(_("skipping missing subrepository: %s\n")
2412 warnings.append(_("skipping missing subrepository: %s\n")
2413 % join(subpath))
2413 % join(subpath))
2414 ui.progress(_('searching'), None)
2414 ui.progress(_('searching'), None)
2415
2415
2416 # warn about failure to delete explicit files/dirs
2416 # warn about failure to delete explicit files/dirs
2417 deleteddirs = util.dirs(deleted)
2417 deleteddirs = util.dirs(deleted)
2418 files = m.files()
2418 files = m.files()
2419 total = len(files)
2419 total = len(files)
2420 count = 0
2420 count = 0
2421 for f in files:
2421 for f in files:
2422 def insubrepo():
2422 def insubrepo():
2423 for subpath in wctx.substate:
2423 for subpath in wctx.substate:
2424 if f.startswith(subpath + '/'):
2424 if f.startswith(subpath + '/'):
2425 return True
2425 return True
2426 return False
2426 return False
2427
2427
2428 count += 1
2428 count += 1
2429 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2429 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2430 isdir = f in deleteddirs or wctx.hasdir(f)
2430 isdir = f in deleteddirs or wctx.hasdir(f)
2431 if (f in repo.dirstate or isdir or f == '.'
2431 if (f in repo.dirstate or isdir or f == '.'
2432 or insubrepo() or f in subs):
2432 or insubrepo() or f in subs):
2433 continue
2433 continue
2434
2434
2435 if repo.wvfs.exists(f):
2435 if repo.wvfs.exists(f):
2436 if repo.wvfs.isdir(f):
2436 if repo.wvfs.isdir(f):
2437 warnings.append(_('not removing %s: no tracked files\n')
2437 warnings.append(_('not removing %s: no tracked files\n')
2438 % m.rel(f))
2438 % m.rel(f))
2439 else:
2439 else:
2440 warnings.append(_('not removing %s: file is untracked\n')
2440 warnings.append(_('not removing %s: file is untracked\n')
2441 % m.rel(f))
2441 % m.rel(f))
2442 # missing files will generate a warning elsewhere
2442 # missing files will generate a warning elsewhere
2443 ret = 1
2443 ret = 1
2444 ui.progress(_('deleting'), None)
2444 ui.progress(_('deleting'), None)
2445
2445
2446 if force:
2446 if force:
2447 list = modified + deleted + clean + added
2447 list = modified + deleted + clean + added
2448 elif after:
2448 elif after:
2449 list = deleted
2449 list = deleted
2450 remaining = modified + added + clean
2450 remaining = modified + added + clean
2451 total = len(remaining)
2451 total = len(remaining)
2452 count = 0
2452 count = 0
2453 for f in remaining:
2453 for f in remaining:
2454 count += 1
2454 count += 1
2455 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2455 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2456 warnings.append(_('not removing %s: file still exists\n')
2456 warnings.append(_('not removing %s: file still exists\n')
2457 % m.rel(f))
2457 % m.rel(f))
2458 ret = 1
2458 ret = 1
2459 ui.progress(_('skipping'), None)
2459 ui.progress(_('skipping'), None)
2460 else:
2460 else:
2461 list = deleted + clean
2461 list = deleted + clean
2462 total = len(modified) + len(added)
2462 total = len(modified) + len(added)
2463 count = 0
2463 count = 0
2464 for f in modified:
2464 for f in modified:
2465 count += 1
2465 count += 1
2466 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2466 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2467 warnings.append(_('not removing %s: file is modified (use -f'
2467 warnings.append(_('not removing %s: file is modified (use -f'
2468 ' to force removal)\n') % m.rel(f))
2468 ' to force removal)\n') % m.rel(f))
2469 ret = 1
2469 ret = 1
2470 for f in added:
2470 for f in added:
2471 count += 1
2471 count += 1
2472 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2472 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2473 warnings.append(_("not removing %s: file has been marked for add"
2473 warnings.append(_("not removing %s: file has been marked for add"
2474 " (use 'hg forget' to undo add)\n") % m.rel(f))
2474 " (use 'hg forget' to undo add)\n") % m.rel(f))
2475 ret = 1
2475 ret = 1
2476 ui.progress(_('skipping'), None)
2476 ui.progress(_('skipping'), None)
2477
2477
2478 list = sorted(list)
2478 list = sorted(list)
2479 total = len(list)
2479 total = len(list)
2480 count = 0
2480 count = 0
2481 for f in list:
2481 for f in list:
2482 count += 1
2482 count += 1
2483 if ui.verbose or not m.exact(f):
2483 if ui.verbose or not m.exact(f):
2484 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2484 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2485 ui.status(_('removing %s\n') % m.rel(f))
2485 ui.status(_('removing %s\n') % m.rel(f))
2486 ui.progress(_('deleting'), None)
2486 ui.progress(_('deleting'), None)
2487
2487
2488 with repo.wlock():
2488 with repo.wlock():
2489 if not after:
2489 if not after:
2490 for f in list:
2490 for f in list:
2491 if f in added:
2491 if f in added:
2492 continue # we never unlink added files on remove
2492 continue # we never unlink added files on remove
2493 repo.wvfs.unlinkpath(f, ignoremissing=True)
2493 repo.wvfs.unlinkpath(f, ignoremissing=True)
2494 repo[None].forget(list)
2494 repo[None].forget(list)
2495
2495
2496 if warn:
2496 if warn:
2497 for warning in warnings:
2497 for warning in warnings:
2498 ui.warn(warning)
2498 ui.warn(warning)
2499
2499
2500 return ret
2500 return ret
2501
2501
2502 def cat(ui, repo, ctx, matcher, prefix, **opts):
2502 def cat(ui, repo, ctx, matcher, prefix, **opts):
2503 err = 1
2503 err = 1
2504
2504
2505 def write(path):
2505 def write(path):
2506 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2506 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2507 pathname=os.path.join(prefix, path))
2507 pathname=os.path.join(prefix, path))
2508 data = ctx[path].data()
2508 data = ctx[path].data()
2509 if opts.get('decode'):
2509 if opts.get('decode'):
2510 data = repo.wwritedata(path, data)
2510 data = repo.wwritedata(path, data)
2511 fp.write(data)
2511 fp.write(data)
2512 fp.close()
2512 fp.close()
2513
2513
2514 # Automation often uses hg cat on single files, so special case it
2514 # Automation often uses hg cat on single files, so special case it
2515 # for performance to avoid the cost of parsing the manifest.
2515 # for performance to avoid the cost of parsing the manifest.
2516 if len(matcher.files()) == 1 and not matcher.anypats():
2516 if len(matcher.files()) == 1 and not matcher.anypats():
2517 file = matcher.files()[0]
2517 file = matcher.files()[0]
2518 mfl = repo.manifestlog
2518 mfl = repo.manifestlog
2519 mfnode = ctx.manifestnode()
2519 mfnode = ctx.manifestnode()
2520 try:
2520 try:
2521 if mfnode and mfl[mfnode].find(file)[0]:
2521 if mfnode and mfl[mfnode].find(file)[0]:
2522 write(file)
2522 write(file)
2523 return 0
2523 return 0
2524 except KeyError:
2524 except KeyError:
2525 pass
2525 pass
2526
2526
2527 for abs in ctx.walk(matcher):
2527 for abs in ctx.walk(matcher):
2528 write(abs)
2528 write(abs)
2529 err = 0
2529 err = 0
2530
2530
2531 for subpath in sorted(ctx.substate):
2531 for subpath in sorted(ctx.substate):
2532 sub = ctx.sub(subpath)
2532 sub = ctx.sub(subpath)
2533 try:
2533 try:
2534 submatch = matchmod.subdirmatcher(subpath, matcher)
2534 submatch = matchmod.subdirmatcher(subpath, matcher)
2535
2535
2536 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2536 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2537 **opts):
2537 **opts):
2538 err = 0
2538 err = 0
2539 except error.RepoLookupError:
2539 except error.RepoLookupError:
2540 ui.status(_("skipping missing subrepository: %s\n")
2540 ui.status(_("skipping missing subrepository: %s\n")
2541 % os.path.join(prefix, subpath))
2541 % os.path.join(prefix, subpath))
2542
2542
2543 return err
2543 return err
2544
2544
2545 def commit(ui, repo, commitfunc, pats, opts):
2545 def commit(ui, repo, commitfunc, pats, opts):
2546 '''commit the specified files or all outstanding changes'''
2546 '''commit the specified files or all outstanding changes'''
2547 date = opts.get('date')
2547 date = opts.get('date')
2548 if date:
2548 if date:
2549 opts['date'] = util.parsedate(date)
2549 opts['date'] = util.parsedate(date)
2550 message = logmessage(ui, opts)
2550 message = logmessage(ui, opts)
2551 matcher = scmutil.match(repo[None], pats, opts)
2551 matcher = scmutil.match(repo[None], pats, opts)
2552
2552
2553 # extract addremove carefully -- this function can be called from a command
2553 # extract addremove carefully -- this function can be called from a command
2554 # that doesn't support addremove
2554 # that doesn't support addremove
2555 if opts.get('addremove'):
2555 if opts.get('addremove'):
2556 if scmutil.addremove(repo, matcher, "", opts) != 0:
2556 if scmutil.addremove(repo, matcher, "", opts) != 0:
2557 raise error.Abort(
2557 raise error.Abort(
2558 _("failed to mark all new/missing files as added/removed"))
2558 _("failed to mark all new/missing files as added/removed"))
2559
2559
2560 return commitfunc(ui, repo, message, matcher, opts)
2560 return commitfunc(ui, repo, message, matcher, opts)
2561
2561
2562 def samefile(f, ctx1, ctx2):
2562 def samefile(f, ctx1, ctx2):
2563 if f in ctx1.manifest():
2563 if f in ctx1.manifest():
2564 a = ctx1.filectx(f)
2564 a = ctx1.filectx(f)
2565 if f in ctx2.manifest():
2565 if f in ctx2.manifest():
2566 b = ctx2.filectx(f)
2566 b = ctx2.filectx(f)
2567 return (not a.cmp(b)
2567 return (not a.cmp(b)
2568 and a.flags() == b.flags())
2568 and a.flags() == b.flags())
2569 else:
2569 else:
2570 return False
2570 return False
2571 else:
2571 else:
2572 return f not in ctx2.manifest()
2572 return f not in ctx2.manifest()
2573
2573
2574 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2574 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2575 # avoid cycle context -> subrepo -> cmdutil
2575 # avoid cycle context -> subrepo -> cmdutil
2576 from . import context
2576 from . import context
2577
2577
2578 # amend will reuse the existing user if not specified, but the obsolete
2578 # amend will reuse the existing user if not specified, but the obsolete
2579 # marker creation requires that the current user's name is specified.
2579 # marker creation requires that the current user's name is specified.
2580 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2580 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2581 ui.username() # raise exception if username not set
2581 ui.username() # raise exception if username not set
2582
2582
2583 ui.note(_('amending changeset %s\n') % old)
2583 ui.note(_('amending changeset %s\n') % old)
2584 base = old.p1()
2584 base = old.p1()
2585 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2585 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2586
2586
2587 wlock = lock = newid = None
2587 wlock = lock = newid = None
2588 try:
2588 try:
2589 wlock = repo.wlock()
2589 wlock = repo.wlock()
2590 lock = repo.lock()
2590 lock = repo.lock()
2591 with repo.transaction('amend') as tr:
2591 with repo.transaction('amend') as tr:
2592 # See if we got a message from -m or -l, if not, open the editor
2592 # See if we got a message from -m or -l, if not, open the editor
2593 # with the message of the changeset to amend
2593 # with the message of the changeset to amend
2594 message = logmessage(ui, opts)
2594 message = logmessage(ui, opts)
2595 # ensure logfile does not conflict with later enforcement of the
2595 # ensure logfile does not conflict with later enforcement of the
2596 # message. potential logfile content has been processed by
2596 # message. potential logfile content has been processed by
2597 # `logmessage` anyway.
2597 # `logmessage` anyway.
2598 opts.pop('logfile')
2598 opts.pop('logfile')
2599 # First, do a regular commit to record all changes in the working
2599 # First, do a regular commit to record all changes in the working
2600 # directory (if there are any)
2600 # directory (if there are any)
2601 ui.callhooks = False
2601 ui.callhooks = False
2602 activebookmark = repo._bookmarks.active
2602 activebookmark = repo._bookmarks.active
2603 try:
2603 try:
2604 repo._bookmarks.active = None
2604 repo._bookmarks.active = None
2605 opts['message'] = 'temporary amend commit for %s' % old
2605 opts['message'] = 'temporary amend commit for %s' % old
2606 node = commit(ui, repo, commitfunc, pats, opts)
2606 node = commit(ui, repo, commitfunc, pats, opts)
2607 finally:
2607 finally:
2608 repo._bookmarks.active = activebookmark
2608 repo._bookmarks.active = activebookmark
2609 repo._bookmarks.recordchange(tr)
2609 repo._bookmarks.recordchange(tr)
2610 ui.callhooks = True
2610 ui.callhooks = True
2611 ctx = repo[node]
2611 ctx = repo[node]
2612
2612
2613 # Participating changesets:
2613 # Participating changesets:
2614 #
2614 #
2615 # node/ctx o - new (intermediate) commit that contains changes
2615 # node/ctx o - new (intermediate) commit that contains changes
2616 # | from working dir to go into amending commit
2616 # | from working dir to go into amending commit
2617 # | (or a workingctx if there were no changes)
2617 # | (or a workingctx if there were no changes)
2618 # |
2618 # |
2619 # old o - changeset to amend
2619 # old o - changeset to amend
2620 # |
2620 # |
2621 # base o - parent of amending changeset
2621 # base o - parent of amending changeset
2622
2622
2623 # Update extra dict from amended commit (e.g. to preserve graft
2623 # Update extra dict from amended commit (e.g. to preserve graft
2624 # source)
2624 # source)
2625 extra.update(old.extra())
2625 extra.update(old.extra())
2626
2626
2627 # Also update it from the intermediate commit or from the wctx
2627 # Also update it from the intermediate commit or from the wctx
2628 extra.update(ctx.extra())
2628 extra.update(ctx.extra())
2629
2629
2630 if len(old.parents()) > 1:
2630 if len(old.parents()) > 1:
2631 # ctx.files() isn't reliable for merges, so fall back to the
2631 # ctx.files() isn't reliable for merges, so fall back to the
2632 # slower repo.status() method
2632 # slower repo.status() method
2633 files = set([fn for st in repo.status(base, old)[:3]
2633 files = set([fn for st in repo.status(base, old)[:3]
2634 for fn in st])
2634 for fn in st])
2635 else:
2635 else:
2636 files = set(old.files())
2636 files = set(old.files())
2637
2637
2638 # Second, we use either the commit we just did, or if there were no
2638 # Second, we use either the commit we just did, or if there were no
2639 # changes the parent of the working directory as the version of the
2639 # changes the parent of the working directory as the version of the
2640 # files in the final amend commit
2640 # files in the final amend commit
2641 if node:
2641 if node:
2642 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2642 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2643
2643
2644 user = ctx.user()
2644 user = ctx.user()
2645 date = ctx.date()
2645 date = ctx.date()
2646 # Recompute copies (avoid recording a -> b -> a)
2646 # Recompute copies (avoid recording a -> b -> a)
2647 copied = copies.pathcopies(base, ctx)
2647 copied = copies.pathcopies(base, ctx)
2648 if old.p2:
2648 if old.p2:
2649 copied.update(copies.pathcopies(old.p2(), ctx))
2649 copied.update(copies.pathcopies(old.p2(), ctx))
2650
2650
2651 # Prune files which were reverted by the updates: if old
2651 # Prune files which were reverted by the updates: if old
2652 # introduced file X and our intermediate commit, node,
2652 # introduced file X and our intermediate commit, node,
2653 # renamed that file, then those two files are the same and
2653 # renamed that file, then those two files are the same and
2654 # we can discard X from our list of files. Likewise if X
2654 # we can discard X from our list of files. Likewise if X
2655 # was deleted, it's no longer relevant
2655 # was deleted, it's no longer relevant
2656 files.update(ctx.files())
2656 files.update(ctx.files())
2657 files = [f for f in files if not samefile(f, ctx, base)]
2657 files = [f for f in files if not samefile(f, ctx, base)]
2658
2658
2659 def filectxfn(repo, ctx_, path):
2659 def filectxfn(repo, ctx_, path):
2660 try:
2660 try:
2661 fctx = ctx[path]
2661 fctx = ctx[path]
2662 flags = fctx.flags()
2662 flags = fctx.flags()
2663 mctx = context.memfilectx(repo,
2663 mctx = context.memfilectx(repo,
2664 fctx.path(), fctx.data(),
2664 fctx.path(), fctx.data(),
2665 islink='l' in flags,
2665 islink='l' in flags,
2666 isexec='x' in flags,
2666 isexec='x' in flags,
2667 copied=copied.get(path))
2667 copied=copied.get(path))
2668 return mctx
2668 return mctx
2669 except KeyError:
2669 except KeyError:
2670 return None
2670 return None
2671 else:
2671 else:
2672 ui.note(_('copying changeset %s to %s\n') % (old, base))
2672 ui.note(_('copying changeset %s to %s\n') % (old, base))
2673
2673
2674 # Use version of files as in the old cset
2674 # Use version of files as in the old cset
2675 def filectxfn(repo, ctx_, path):
2675 def filectxfn(repo, ctx_, path):
2676 try:
2676 try:
2677 return old.filectx(path)
2677 return old.filectx(path)
2678 except KeyError:
2678 except KeyError:
2679 return None
2679 return None
2680
2680
2681 user = opts.get('user') or old.user()
2681 user = opts.get('user') or old.user()
2682 date = opts.get('date') or old.date()
2682 date = opts.get('date') or old.date()
2683 editform = mergeeditform(old, 'commit.amend')
2683 editform = mergeeditform(old, 'commit.amend')
2684 editor = getcommiteditor(editform=editform, **opts)
2684 editor = getcommiteditor(editform=editform, **opts)
2685 if not message:
2685 if not message:
2686 editor = getcommiteditor(edit=True, editform=editform)
2686 editor = getcommiteditor(edit=True, editform=editform)
2687 message = old.description()
2687 message = old.description()
2688
2688
2689 pureextra = extra.copy()
2689 pureextra = extra.copy()
2690 extra['amend_source'] = old.hex()
2690 extra['amend_source'] = old.hex()
2691
2691
2692 new = context.memctx(repo,
2692 new = context.memctx(repo,
2693 parents=[base.node(), old.p2().node()],
2693 parents=[base.node(), old.p2().node()],
2694 text=message,
2694 text=message,
2695 files=files,
2695 files=files,
2696 filectxfn=filectxfn,
2696 filectxfn=filectxfn,
2697 user=user,
2697 user=user,
2698 date=date,
2698 date=date,
2699 extra=extra,
2699 extra=extra,
2700 editor=editor)
2700 editor=editor)
2701
2701
2702 newdesc = changelog.stripdesc(new.description())
2702 newdesc = changelog.stripdesc(new.description())
2703 if ((not node)
2703 if ((not node)
2704 and newdesc == old.description()
2704 and newdesc == old.description()
2705 and user == old.user()
2705 and user == old.user()
2706 and date == old.date()
2706 and date == old.date()
2707 and pureextra == old.extra()):
2707 and pureextra == old.extra()):
2708 # nothing changed. continuing here would create a new node
2708 # nothing changed. continuing here would create a new node
2709 # anyway because of the amend_source noise.
2709 # anyway because of the amend_source noise.
2710 #
2710 #
2711 # This not what we expect from amend.
2711 # This not what we expect from amend.
2712 return old.node()
2712 return old.node()
2713
2713
2714 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2714 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2715 try:
2715 try:
2716 if opts.get('secret'):
2716 if opts.get('secret'):
2717 commitphase = 'secret'
2717 commitphase = 'secret'
2718 else:
2718 else:
2719 commitphase = old.phase()
2719 commitphase = old.phase()
2720 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2720 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2721 newid = repo.commitctx(new)
2721 newid = repo.commitctx(new)
2722 finally:
2722 finally:
2723 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2723 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2724 if newid != old.node():
2724 if newid != old.node():
2725 # Reroute the working copy parent to the new changeset
2725 # Reroute the working copy parent to the new changeset
2726 repo.setparents(newid, nullid)
2726 repo.setparents(newid, nullid)
2727
2727
2728 # Move bookmarks from old parent to amend commit
2728 # Move bookmarks from old parent to amend commit
2729 bms = repo.nodebookmarks(old.node())
2729 bms = repo.nodebookmarks(old.node())
2730 if bms:
2730 if bms:
2731 marks = repo._bookmarks
2731 marks = repo._bookmarks
2732 for bm in bms:
2732 for bm in bms:
2733 ui.debug('moving bookmarks %r from %s to %s\n' %
2733 ui.debug('moving bookmarks %r from %s to %s\n' %
2734 (marks, old.hex(), hex(newid)))
2734 (marks, old.hex(), hex(newid)))
2735 marks[bm] = newid
2735 marks[bm] = newid
2736 marks.recordchange(tr)
2736 marks.recordchange(tr)
2737 #commit the whole amend process
2737 #commit the whole amend process
2738 if createmarkers:
2738 if createmarkers:
2739 # mark the new changeset as successor of the rewritten one
2739 # mark the new changeset as successor of the rewritten one
2740 new = repo[newid]
2740 new = repo[newid]
2741 obs = [(old, (new,))]
2741 obs = [(old, (new,))]
2742 if node:
2742 if node:
2743 obs.append((ctx, ()))
2743 obs.append((ctx, ()))
2744
2744
2745 obsolete.createmarkers(repo, obs, operation='amend')
2745 obsolete.createmarkers(repo, obs, operation='amend')
2746 if not createmarkers and newid != old.node():
2746 if not createmarkers and newid != old.node():
2747 # Strip the intermediate commit (if there was one) and the amended
2747 # Strip the intermediate commit (if there was one) and the amended
2748 # commit
2748 # commit
2749 if node:
2749 if node:
2750 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2750 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2751 ui.note(_('stripping amended changeset %s\n') % old)
2751 ui.note(_('stripping amended changeset %s\n') % old)
2752 repair.strip(ui, repo, old.node(), topic='amend-backup')
2752 repair.strip(ui, repo, old.node(), topic='amend-backup')
2753 finally:
2753 finally:
2754 lockmod.release(lock, wlock)
2754 lockmod.release(lock, wlock)
2755 return newid
2755 return newid
2756
2756
2757 def commiteditor(repo, ctx, subs, editform=''):
2757 def commiteditor(repo, ctx, subs, editform=''):
2758 if ctx.description():
2758 if ctx.description():
2759 return ctx.description()
2759 return ctx.description()
2760 return commitforceeditor(repo, ctx, subs, editform=editform,
2760 return commitforceeditor(repo, ctx, subs, editform=editform,
2761 unchangedmessagedetection=True)
2761 unchangedmessagedetection=True)
2762
2762
2763 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2763 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2764 editform='', unchangedmessagedetection=False):
2764 editform='', unchangedmessagedetection=False):
2765 if not extramsg:
2765 if not extramsg:
2766 extramsg = _("Leave message empty to abort commit.")
2766 extramsg = _("Leave message empty to abort commit.")
2767
2767
2768 forms = [e for e in editform.split('.') if e]
2768 forms = [e for e in editform.split('.') if e]
2769 forms.insert(0, 'changeset')
2769 forms.insert(0, 'changeset')
2770 templatetext = None
2770 templatetext = None
2771 while forms:
2771 while forms:
2772 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2772 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2773 if tmpl:
2773 if tmpl:
2774 tmpl = templater.unquotestring(tmpl)
2774 tmpl = templater.unquotestring(tmpl)
2775 templatetext = committext = buildcommittemplate(
2775 templatetext = committext = buildcommittemplate(
2776 repo, ctx, subs, extramsg, tmpl)
2776 repo, ctx, subs, extramsg, tmpl)
2777 break
2777 break
2778 forms.pop()
2778 forms.pop()
2779 else:
2779 else:
2780 committext = buildcommittext(repo, ctx, subs, extramsg)
2780 committext = buildcommittext(repo, ctx, subs, extramsg)
2781
2781
2782 # run editor in the repository root
2782 # run editor in the repository root
2783 olddir = pycompat.getcwd()
2783 olddir = pycompat.getcwd()
2784 os.chdir(repo.root)
2784 os.chdir(repo.root)
2785
2785
2786 # make in-memory changes visible to external process
2786 # make in-memory changes visible to external process
2787 tr = repo.currenttransaction()
2787 tr = repo.currenttransaction()
2788 repo.dirstate.write(tr)
2788 repo.dirstate.write(tr)
2789 pending = tr and tr.writepending() and repo.root
2789 pending = tr and tr.writepending() and repo.root
2790
2790
2791 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2791 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2792 editform=editform, pending=pending,
2792 editform=editform, pending=pending,
2793 repopath=repo.path)
2793 repopath=repo.path)
2794 text = editortext
2794 text = editortext
2795
2795
2796 # strip away anything below this special string (used for editors that want
2796 # strip away anything below this special string (used for editors that want
2797 # to display the diff)
2797 # to display the diff)
2798 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2798 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2799 if stripbelow:
2799 if stripbelow:
2800 text = text[:stripbelow.start()]
2800 text = text[:stripbelow.start()]
2801
2801
2802 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2802 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2803 os.chdir(olddir)
2803 os.chdir(olddir)
2804
2804
2805 if finishdesc:
2805 if finishdesc:
2806 text = finishdesc(text)
2806 text = finishdesc(text)
2807 if not text.strip():
2807 if not text.strip():
2808 raise error.Abort(_("empty commit message"))
2808 raise error.Abort(_("empty commit message"))
2809 if unchangedmessagedetection and editortext == templatetext:
2809 if unchangedmessagedetection and editortext == templatetext:
2810 raise error.Abort(_("commit message unchanged"))
2810 raise error.Abort(_("commit message unchanged"))
2811
2811
2812 return text
2812 return text
2813
2813
2814 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2814 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2815 ui = repo.ui
2815 ui = repo.ui
2816 tmpl, mapfile = gettemplate(ui, tmpl, None)
2816 tmpl, mapfile = gettemplate(ui, tmpl, None)
2817
2817
2818 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2818 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2819
2819
2820 for k, v in repo.ui.configitems('committemplate'):
2820 for k, v in repo.ui.configitems('committemplate'):
2821 if k != 'changeset':
2821 if k != 'changeset':
2822 t.t.cache[k] = v
2822 t.t.cache[k] = v
2823
2823
2824 if not extramsg:
2824 if not extramsg:
2825 extramsg = '' # ensure that extramsg is string
2825 extramsg = '' # ensure that extramsg is string
2826
2826
2827 ui.pushbuffer()
2827 ui.pushbuffer()
2828 t.show(ctx, extramsg=extramsg)
2828 t.show(ctx, extramsg=extramsg)
2829 return ui.popbuffer()
2829 return ui.popbuffer()
2830
2830
2831 def hgprefix(msg):
2831 def hgprefix(msg):
2832 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2832 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2833
2833
2834 def buildcommittext(repo, ctx, subs, extramsg):
2834 def buildcommittext(repo, ctx, subs, extramsg):
2835 edittext = []
2835 edittext = []
2836 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2836 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2837 if ctx.description():
2837 if ctx.description():
2838 edittext.append(ctx.description())
2838 edittext.append(ctx.description())
2839 edittext.append("")
2839 edittext.append("")
2840 edittext.append("") # Empty line between message and comments.
2840 edittext.append("") # Empty line between message and comments.
2841 edittext.append(hgprefix(_("Enter commit message."
2841 edittext.append(hgprefix(_("Enter commit message."
2842 " Lines beginning with 'HG:' are removed.")))
2842 " Lines beginning with 'HG:' are removed.")))
2843 edittext.append(hgprefix(extramsg))
2843 edittext.append(hgprefix(extramsg))
2844 edittext.append("HG: --")
2844 edittext.append("HG: --")
2845 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2845 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2846 if ctx.p2():
2846 if ctx.p2():
2847 edittext.append(hgprefix(_("branch merge")))
2847 edittext.append(hgprefix(_("branch merge")))
2848 if ctx.branch():
2848 if ctx.branch():
2849 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2849 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2850 if bookmarks.isactivewdirparent(repo):
2850 if bookmarks.isactivewdirparent(repo):
2851 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2851 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2852 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2852 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2853 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2853 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2854 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2854 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2855 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2855 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2856 if not added and not modified and not removed:
2856 if not added and not modified and not removed:
2857 edittext.append(hgprefix(_("no files changed")))
2857 edittext.append(hgprefix(_("no files changed")))
2858 edittext.append("")
2858 edittext.append("")
2859
2859
2860 return "\n".join(edittext)
2860 return "\n".join(edittext)
2861
2861
2862 def commitstatus(repo, node, branch, bheads=None, opts=None):
2862 def commitstatus(repo, node, branch, bheads=None, opts=None):
2863 if opts is None:
2863 if opts is None:
2864 opts = {}
2864 opts = {}
2865 ctx = repo[node]
2865 ctx = repo[node]
2866 parents = ctx.parents()
2866 parents = ctx.parents()
2867
2867
2868 if (not opts.get('amend') and bheads and node not in bheads and not
2868 if (not opts.get('amend') and bheads and node not in bheads and not
2869 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2869 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2870 repo.ui.status(_('created new head\n'))
2870 repo.ui.status(_('created new head\n'))
2871 # The message is not printed for initial roots. For the other
2871 # The message is not printed for initial roots. For the other
2872 # changesets, it is printed in the following situations:
2872 # changesets, it is printed in the following situations:
2873 #
2873 #
2874 # Par column: for the 2 parents with ...
2874 # Par column: for the 2 parents with ...
2875 # N: null or no parent
2875 # N: null or no parent
2876 # B: parent is on another named branch
2876 # B: parent is on another named branch
2877 # C: parent is a regular non head changeset
2877 # C: parent is a regular non head changeset
2878 # H: parent was a branch head of the current branch
2878 # H: parent was a branch head of the current branch
2879 # Msg column: whether we print "created new head" message
2879 # Msg column: whether we print "created new head" message
2880 # In the following, it is assumed that there already exists some
2880 # In the following, it is assumed that there already exists some
2881 # initial branch heads of the current branch, otherwise nothing is
2881 # initial branch heads of the current branch, otherwise nothing is
2882 # printed anyway.
2882 # printed anyway.
2883 #
2883 #
2884 # Par Msg Comment
2884 # Par Msg Comment
2885 # N N y additional topo root
2885 # N N y additional topo root
2886 #
2886 #
2887 # B N y additional branch root
2887 # B N y additional branch root
2888 # C N y additional topo head
2888 # C N y additional topo head
2889 # H N n usual case
2889 # H N n usual case
2890 #
2890 #
2891 # B B y weird additional branch root
2891 # B B y weird additional branch root
2892 # C B y branch merge
2892 # C B y branch merge
2893 # H B n merge with named branch
2893 # H B n merge with named branch
2894 #
2894 #
2895 # C C y additional head from merge
2895 # C C y additional head from merge
2896 # C H n merge with a head
2896 # C H n merge with a head
2897 #
2897 #
2898 # H H n head merge: head count decreases
2898 # H H n head merge: head count decreases
2899
2899
2900 if not opts.get('close_branch'):
2900 if not opts.get('close_branch'):
2901 for r in parents:
2901 for r in parents:
2902 if r.closesbranch() and r.branch() == branch:
2902 if r.closesbranch() and r.branch() == branch:
2903 repo.ui.status(_('reopening closed branch head %d\n') % r)
2903 repo.ui.status(_('reopening closed branch head %d\n') % r)
2904
2904
2905 if repo.ui.debugflag:
2905 if repo.ui.debugflag:
2906 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2906 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2907 elif repo.ui.verbose:
2907 elif repo.ui.verbose:
2908 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2908 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2909
2909
2910 def postcommitstatus(repo, pats, opts):
2910 def postcommitstatus(repo, pats, opts):
2911 return repo.status(match=scmutil.match(repo[None], pats, opts))
2911 return repo.status(match=scmutil.match(repo[None], pats, opts))
2912
2912
2913 def revert(ui, repo, ctx, parents, *pats, **opts):
2913 def revert(ui, repo, ctx, parents, *pats, **opts):
2914 parent, p2 = parents
2914 parent, p2 = parents
2915 node = ctx.node()
2915 node = ctx.node()
2916
2916
2917 mf = ctx.manifest()
2917 mf = ctx.manifest()
2918 if node == p2:
2918 if node == p2:
2919 parent = p2
2919 parent = p2
2920
2920
2921 # need all matching names in dirstate and manifest of target rev,
2921 # need all matching names in dirstate and manifest of target rev,
2922 # so have to walk both. do not print errors if files exist in one
2922 # so have to walk both. do not print errors if files exist in one
2923 # but not other. in both cases, filesets should be evaluated against
2923 # but not other. in both cases, filesets should be evaluated against
2924 # workingctx to get consistent result (issue4497). this means 'set:**'
2924 # workingctx to get consistent result (issue4497). this means 'set:**'
2925 # cannot be used to select missing files from target rev.
2925 # cannot be used to select missing files from target rev.
2926
2926
2927 # `names` is a mapping for all elements in working copy and target revision
2927 # `names` is a mapping for all elements in working copy and target revision
2928 # The mapping is in the form:
2928 # The mapping is in the form:
2929 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2929 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2930 names = {}
2930 names = {}
2931
2931
2932 with repo.wlock():
2932 with repo.wlock():
2933 ## filling of the `names` mapping
2933 ## filling of the `names` mapping
2934 # walk dirstate to fill `names`
2934 # walk dirstate to fill `names`
2935
2935
2936 interactive = opts.get('interactive', False)
2936 interactive = opts.get('interactive', False)
2937 wctx = repo[None]
2937 wctx = repo[None]
2938 m = scmutil.match(wctx, pats, opts)
2938 m = scmutil.match(wctx, pats, opts)
2939
2939
2940 # we'll need this later
2940 # we'll need this later
2941 targetsubs = sorted(s for s in wctx.substate if m(s))
2941 targetsubs = sorted(s for s in wctx.substate if m(s))
2942
2942
2943 if not m.always():
2943 if not m.always():
2944 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2944 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2945 names[abs] = m.rel(abs), m.exact(abs)
2945 names[abs] = m.rel(abs), m.exact(abs)
2946
2946
2947 # walk target manifest to fill `names`
2947 # walk target manifest to fill `names`
2948
2948
2949 def badfn(path, msg):
2949 def badfn(path, msg):
2950 if path in names:
2950 if path in names:
2951 return
2951 return
2952 if path in ctx.substate:
2952 if path in ctx.substate:
2953 return
2953 return
2954 path_ = path + '/'
2954 path_ = path + '/'
2955 for f in names:
2955 for f in names:
2956 if f.startswith(path_):
2956 if f.startswith(path_):
2957 return
2957 return
2958 ui.warn("%s: %s\n" % (m.rel(path), msg))
2958 ui.warn("%s: %s\n" % (m.rel(path), msg))
2959
2959
2960 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2960 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2961 if abs not in names:
2961 if abs not in names:
2962 names[abs] = m.rel(abs), m.exact(abs)
2962 names[abs] = m.rel(abs), m.exact(abs)
2963
2963
2964 # Find status of all file in `names`.
2964 # Find status of all file in `names`.
2965 m = scmutil.matchfiles(repo, names)
2965 m = scmutil.matchfiles(repo, names)
2966
2966
2967 changes = repo.status(node1=node, match=m,
2967 changes = repo.status(node1=node, match=m,
2968 unknown=True, ignored=True, clean=True)
2968 unknown=True, ignored=True, clean=True)
2969 else:
2969 else:
2970 changes = repo.status(node1=node, match=m)
2970 changes = repo.status(node1=node, match=m)
2971 for kind in changes:
2971 for kind in changes:
2972 for abs in kind:
2972 for abs in kind:
2973 names[abs] = m.rel(abs), m.exact(abs)
2973 names[abs] = m.rel(abs), m.exact(abs)
2974
2974
2975 m = scmutil.matchfiles(repo, names)
2975 m = scmutil.matchfiles(repo, names)
2976
2976
2977 modified = set(changes.modified)
2977 modified = set(changes.modified)
2978 added = set(changes.added)
2978 added = set(changes.added)
2979 removed = set(changes.removed)
2979 removed = set(changes.removed)
2980 _deleted = set(changes.deleted)
2980 _deleted = set(changes.deleted)
2981 unknown = set(changes.unknown)
2981 unknown = set(changes.unknown)
2982 unknown.update(changes.ignored)
2982 unknown.update(changes.ignored)
2983 clean = set(changes.clean)
2983 clean = set(changes.clean)
2984 modadded = set()
2984 modadded = set()
2985
2985
2986 # We need to account for the state of the file in the dirstate,
2986 # We need to account for the state of the file in the dirstate,
2987 # even when we revert against something else than parent. This will
2987 # even when we revert against something else than parent. This will
2988 # slightly alter the behavior of revert (doing back up or not, delete
2988 # slightly alter the behavior of revert (doing back up or not, delete
2989 # or just forget etc).
2989 # or just forget etc).
2990 if parent == node:
2990 if parent == node:
2991 dsmodified = modified
2991 dsmodified = modified
2992 dsadded = added
2992 dsadded = added
2993 dsremoved = removed
2993 dsremoved = removed
2994 # store all local modifications, useful later for rename detection
2994 # store all local modifications, useful later for rename detection
2995 localchanges = dsmodified | dsadded
2995 localchanges = dsmodified | dsadded
2996 modified, added, removed = set(), set(), set()
2996 modified, added, removed = set(), set(), set()
2997 else:
2997 else:
2998 changes = repo.status(node1=parent, match=m)
2998 changes = repo.status(node1=parent, match=m)
2999 dsmodified = set(changes.modified)
2999 dsmodified = set(changes.modified)
3000 dsadded = set(changes.added)
3000 dsadded = set(changes.added)
3001 dsremoved = set(changes.removed)
3001 dsremoved = set(changes.removed)
3002 # store all local modifications, useful later for rename detection
3002 # store all local modifications, useful later for rename detection
3003 localchanges = dsmodified | dsadded
3003 localchanges = dsmodified | dsadded
3004
3004
3005 # only take into account for removes between wc and target
3005 # only take into account for removes between wc and target
3006 clean |= dsremoved - removed
3006 clean |= dsremoved - removed
3007 dsremoved &= removed
3007 dsremoved &= removed
3008 # distinct between dirstate remove and other
3008 # distinct between dirstate remove and other
3009 removed -= dsremoved
3009 removed -= dsremoved
3010
3010
3011 modadded = added & dsmodified
3011 modadded = added & dsmodified
3012 added -= modadded
3012 added -= modadded
3013
3013
3014 # tell newly modified apart.
3014 # tell newly modified apart.
3015 dsmodified &= modified
3015 dsmodified &= modified
3016 dsmodified |= modified & dsadded # dirstate added may need backup
3016 dsmodified |= modified & dsadded # dirstate added may need backup
3017 modified -= dsmodified
3017 modified -= dsmodified
3018
3018
3019 # We need to wait for some post-processing to update this set
3019 # We need to wait for some post-processing to update this set
3020 # before making the distinction. The dirstate will be used for
3020 # before making the distinction. The dirstate will be used for
3021 # that purpose.
3021 # that purpose.
3022 dsadded = added
3022 dsadded = added
3023
3023
3024 # in case of merge, files that are actually added can be reported as
3024 # in case of merge, files that are actually added can be reported as
3025 # modified, we need to post process the result
3025 # modified, we need to post process the result
3026 if p2 != nullid:
3026 if p2 != nullid:
3027 mergeadd = set(dsmodified)
3027 mergeadd = set(dsmodified)
3028 for path in dsmodified:
3028 for path in dsmodified:
3029 if path in mf:
3029 if path in mf:
3030 mergeadd.remove(path)
3030 mergeadd.remove(path)
3031 dsadded |= mergeadd
3031 dsadded |= mergeadd
3032 dsmodified -= mergeadd
3032 dsmodified -= mergeadd
3033
3033
3034 # if f is a rename, update `names` to also revert the source
3034 # if f is a rename, update `names` to also revert the source
3035 cwd = repo.getcwd()
3035 cwd = repo.getcwd()
3036 for f in localchanges:
3036 for f in localchanges:
3037 src = repo.dirstate.copied(f)
3037 src = repo.dirstate.copied(f)
3038 # XXX should we check for rename down to target node?
3038 # XXX should we check for rename down to target node?
3039 if src and src not in names and repo.dirstate[src] == 'r':
3039 if src and src not in names and repo.dirstate[src] == 'r':
3040 dsremoved.add(src)
3040 dsremoved.add(src)
3041 names[src] = (repo.pathto(src, cwd), True)
3041 names[src] = (repo.pathto(src, cwd), True)
3042
3042
3043 # determine the exact nature of the deleted changesets
3043 # determine the exact nature of the deleted changesets
3044 deladded = set(_deleted)
3044 deladded = set(_deleted)
3045 for path in _deleted:
3045 for path in _deleted:
3046 if path in mf:
3046 if path in mf:
3047 deladded.remove(path)
3047 deladded.remove(path)
3048 deleted = _deleted - deladded
3048 deleted = _deleted - deladded
3049
3049
3050 # distinguish between file to forget and the other
3050 # distinguish between file to forget and the other
3051 added = set()
3051 added = set()
3052 for abs in dsadded:
3052 for abs in dsadded:
3053 if repo.dirstate[abs] != 'a':
3053 if repo.dirstate[abs] != 'a':
3054 added.add(abs)
3054 added.add(abs)
3055 dsadded -= added
3055 dsadded -= added
3056
3056
3057 for abs in deladded:
3057 for abs in deladded:
3058 if repo.dirstate[abs] == 'a':
3058 if repo.dirstate[abs] == 'a':
3059 dsadded.add(abs)
3059 dsadded.add(abs)
3060 deladded -= dsadded
3060 deladded -= dsadded
3061
3061
3062 # For files marked as removed, we check if an unknown file is present at
3062 # For files marked as removed, we check if an unknown file is present at
3063 # the same path. If a such file exists it may need to be backed up.
3063 # the same path. If a such file exists it may need to be backed up.
3064 # Making the distinction at this stage helps have simpler backup
3064 # Making the distinction at this stage helps have simpler backup
3065 # logic.
3065 # logic.
3066 removunk = set()
3066 removunk = set()
3067 for abs in removed:
3067 for abs in removed:
3068 target = repo.wjoin(abs)
3068 target = repo.wjoin(abs)
3069 if os.path.lexists(target):
3069 if os.path.lexists(target):
3070 removunk.add(abs)
3070 removunk.add(abs)
3071 removed -= removunk
3071 removed -= removunk
3072
3072
3073 dsremovunk = set()
3073 dsremovunk = set()
3074 for abs in dsremoved:
3074 for abs in dsremoved:
3075 target = repo.wjoin(abs)
3075 target = repo.wjoin(abs)
3076 if os.path.lexists(target):
3076 if os.path.lexists(target):
3077 dsremovunk.add(abs)
3077 dsremovunk.add(abs)
3078 dsremoved -= dsremovunk
3078 dsremoved -= dsremovunk
3079
3079
3080 # action to be actually performed by revert
3080 # action to be actually performed by revert
3081 # (<list of file>, message>) tuple
3081 # (<list of file>, message>) tuple
3082 actions = {'revert': ([], _('reverting %s\n')),
3082 actions = {'revert': ([], _('reverting %s\n')),
3083 'add': ([], _('adding %s\n')),
3083 'add': ([], _('adding %s\n')),
3084 'remove': ([], _('removing %s\n')),
3084 'remove': ([], _('removing %s\n')),
3085 'drop': ([], _('removing %s\n')),
3085 'drop': ([], _('removing %s\n')),
3086 'forget': ([], _('forgetting %s\n')),
3086 'forget': ([], _('forgetting %s\n')),
3087 'undelete': ([], _('undeleting %s\n')),
3087 'undelete': ([], _('undeleting %s\n')),
3088 'noop': (None, _('no changes needed to %s\n')),
3088 'noop': (None, _('no changes needed to %s\n')),
3089 'unknown': (None, _('file not managed: %s\n')),
3089 'unknown': (None, _('file not managed: %s\n')),
3090 }
3090 }
3091
3091
3092 # "constant" that convey the backup strategy.
3092 # "constant" that convey the backup strategy.
3093 # All set to `discard` if `no-backup` is set do avoid checking
3093 # All set to `discard` if `no-backup` is set do avoid checking
3094 # no_backup lower in the code.
3094 # no_backup lower in the code.
3095 # These values are ordered for comparison purposes
3095 # These values are ordered for comparison purposes
3096 backupinteractive = 3 # do backup if interactively modified
3096 backupinteractive = 3 # do backup if interactively modified
3097 backup = 2 # unconditionally do backup
3097 backup = 2 # unconditionally do backup
3098 check = 1 # check if the existing file differs from target
3098 check = 1 # check if the existing file differs from target
3099 discard = 0 # never do backup
3099 discard = 0 # never do backup
3100 if opts.get('no_backup'):
3100 if opts.get('no_backup'):
3101 backupinteractive = backup = check = discard
3101 backupinteractive = backup = check = discard
3102 if interactive:
3102 if interactive:
3103 dsmodifiedbackup = backupinteractive
3103 dsmodifiedbackup = backupinteractive
3104 else:
3104 else:
3105 dsmodifiedbackup = backup
3105 dsmodifiedbackup = backup
3106 tobackup = set()
3106 tobackup = set()
3107
3107
3108 backupanddel = actions['remove']
3108 backupanddel = actions['remove']
3109 if not opts.get('no_backup'):
3109 if not opts.get('no_backup'):
3110 backupanddel = actions['drop']
3110 backupanddel = actions['drop']
3111
3111
3112 disptable = (
3112 disptable = (
3113 # dispatch table:
3113 # dispatch table:
3114 # file state
3114 # file state
3115 # action
3115 # action
3116 # make backup
3116 # make backup
3117
3117
3118 ## Sets that results that will change file on disk
3118 ## Sets that results that will change file on disk
3119 # Modified compared to target, no local change
3119 # Modified compared to target, no local change
3120 (modified, actions['revert'], discard),
3120 (modified, actions['revert'], discard),
3121 # Modified compared to target, but local file is deleted
3121 # Modified compared to target, but local file is deleted
3122 (deleted, actions['revert'], discard),
3122 (deleted, actions['revert'], discard),
3123 # Modified compared to target, local change
3123 # Modified compared to target, local change
3124 (dsmodified, actions['revert'], dsmodifiedbackup),
3124 (dsmodified, actions['revert'], dsmodifiedbackup),
3125 # Added since target
3125 # Added since target
3126 (added, actions['remove'], discard),
3126 (added, actions['remove'], discard),
3127 # Added in working directory
3127 # Added in working directory
3128 (dsadded, actions['forget'], discard),
3128 (dsadded, actions['forget'], discard),
3129 # Added since target, have local modification
3129 # Added since target, have local modification
3130 (modadded, backupanddel, backup),
3130 (modadded, backupanddel, backup),
3131 # Added since target but file is missing in working directory
3131 # Added since target but file is missing in working directory
3132 (deladded, actions['drop'], discard),
3132 (deladded, actions['drop'], discard),
3133 # Removed since target, before working copy parent
3133 # Removed since target, before working copy parent
3134 (removed, actions['add'], discard),
3134 (removed, actions['add'], discard),
3135 # Same as `removed` but an unknown file exists at the same path
3135 # Same as `removed` but an unknown file exists at the same path
3136 (removunk, actions['add'], check),
3136 (removunk, actions['add'], check),
3137 # Removed since targe, marked as such in working copy parent
3137 # Removed since targe, marked as such in working copy parent
3138 (dsremoved, actions['undelete'], discard),
3138 (dsremoved, actions['undelete'], discard),
3139 # Same as `dsremoved` but an unknown file exists at the same path
3139 # Same as `dsremoved` but an unknown file exists at the same path
3140 (dsremovunk, actions['undelete'], check),
3140 (dsremovunk, actions['undelete'], check),
3141 ## the following sets does not result in any file changes
3141 ## the following sets does not result in any file changes
3142 # File with no modification
3142 # File with no modification
3143 (clean, actions['noop'], discard),
3143 (clean, actions['noop'], discard),
3144 # Existing file, not tracked anywhere
3144 # Existing file, not tracked anywhere
3145 (unknown, actions['unknown'], discard),
3145 (unknown, actions['unknown'], discard),
3146 )
3146 )
3147
3147
3148 for abs, (rel, exact) in sorted(names.items()):
3148 for abs, (rel, exact) in sorted(names.items()):
3149 # target file to be touch on disk (relative to cwd)
3149 # target file to be touch on disk (relative to cwd)
3150 target = repo.wjoin(abs)
3150 target = repo.wjoin(abs)
3151 # search the entry in the dispatch table.
3151 # search the entry in the dispatch table.
3152 # if the file is in any of these sets, it was touched in the working
3152 # if the file is in any of these sets, it was touched in the working
3153 # directory parent and we are sure it needs to be reverted.
3153 # directory parent and we are sure it needs to be reverted.
3154 for table, (xlist, msg), dobackup in disptable:
3154 for table, (xlist, msg), dobackup in disptable:
3155 if abs not in table:
3155 if abs not in table:
3156 continue
3156 continue
3157 if xlist is not None:
3157 if xlist is not None:
3158 xlist.append(abs)
3158 xlist.append(abs)
3159 if dobackup:
3159 if dobackup:
3160 # If in interactive mode, don't automatically create
3160 # If in interactive mode, don't automatically create
3161 # .orig files (issue4793)
3161 # .orig files (issue4793)
3162 if dobackup == backupinteractive:
3162 if dobackup == backupinteractive:
3163 tobackup.add(abs)
3163 tobackup.add(abs)
3164 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3164 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3165 bakname = scmutil.origpath(ui, repo, rel)
3165 bakname = scmutil.origpath(ui, repo, rel)
3166 ui.note(_('saving current version of %s as %s\n') %
3166 ui.note(_('saving current version of %s as %s\n') %
3167 (rel, bakname))
3167 (rel, bakname))
3168 if not opts.get('dry_run'):
3168 if not opts.get('dry_run'):
3169 if interactive:
3169 if interactive:
3170 util.copyfile(target, bakname)
3170 util.copyfile(target, bakname)
3171 else:
3171 else:
3172 util.rename(target, bakname)
3172 util.rename(target, bakname)
3173 if ui.verbose or not exact:
3173 if ui.verbose or not exact:
3174 if not isinstance(msg, basestring):
3174 if not isinstance(msg, basestring):
3175 msg = msg(abs)
3175 msg = msg(abs)
3176 ui.status(msg % rel)
3176 ui.status(msg % rel)
3177 elif exact:
3177 elif exact:
3178 ui.warn(msg % rel)
3178 ui.warn(msg % rel)
3179 break
3179 break
3180
3180
3181 if not opts.get('dry_run'):
3181 if not opts.get('dry_run'):
3182 needdata = ('revert', 'add', 'undelete')
3182 needdata = ('revert', 'add', 'undelete')
3183 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3183 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3184 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3184 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3185
3185
3186 if targetsubs:
3186 if targetsubs:
3187 # Revert the subrepos on the revert list
3187 # Revert the subrepos on the revert list
3188 for sub in targetsubs:
3188 for sub in targetsubs:
3189 try:
3189 try:
3190 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3190 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3191 except KeyError:
3191 except KeyError:
3192 raise error.Abort("subrepository '%s' does not exist in %s!"
3192 raise error.Abort("subrepository '%s' does not exist in %s!"
3193 % (sub, short(ctx.node())))
3193 % (sub, short(ctx.node())))
3194
3194
3195 def _revertprefetch(repo, ctx, *files):
3195 def _revertprefetch(repo, ctx, *files):
3196 """Let extension changing the storage layer prefetch content"""
3196 """Let extension changing the storage layer prefetch content"""
3197 pass
3197 pass
3198
3198
3199 def _performrevert(repo, parents, ctx, actions, interactive=False,
3199 def _performrevert(repo, parents, ctx, actions, interactive=False,
3200 tobackup=None):
3200 tobackup=None):
3201 """function that actually perform all the actions computed for revert
3201 """function that actually perform all the actions computed for revert
3202
3202
3203 This is an independent function to let extension to plug in and react to
3203 This is an independent function to let extension to plug in and react to
3204 the imminent revert.
3204 the imminent revert.
3205
3205
3206 Make sure you have the working directory locked when calling this function.
3206 Make sure you have the working directory locked when calling this function.
3207 """
3207 """
3208 parent, p2 = parents
3208 parent, p2 = parents
3209 node = ctx.node()
3209 node = ctx.node()
3210 excluded_files = []
3210 excluded_files = []
3211 matcher_opts = {"exclude": excluded_files}
3211 matcher_opts = {"exclude": excluded_files}
3212
3212
3213 def checkout(f):
3213 def checkout(f):
3214 fc = ctx[f]
3214 fc = ctx[f]
3215 repo.wwrite(f, fc.data(), fc.flags())
3215 repo.wwrite(f, fc.data(), fc.flags())
3216
3216
3217 def doremove(f):
3217 def doremove(f):
3218 try:
3218 try:
3219 repo.wvfs.unlinkpath(f)
3219 repo.wvfs.unlinkpath(f)
3220 except OSError:
3220 except OSError:
3221 pass
3221 pass
3222 repo.dirstate.remove(f)
3222 repo.dirstate.remove(f)
3223
3223
3224 audit_path = pathutil.pathauditor(repo.root)
3224 audit_path = pathutil.pathauditor(repo.root)
3225 for f in actions['forget'][0]:
3225 for f in actions['forget'][0]:
3226 if interactive:
3226 if interactive:
3227 choice = repo.ui.promptchoice(
3227 choice = repo.ui.promptchoice(
3228 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3228 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3229 if choice == 0:
3229 if choice == 0:
3230 repo.dirstate.drop(f)
3230 repo.dirstate.drop(f)
3231 else:
3231 else:
3232 excluded_files.append(repo.wjoin(f))
3232 excluded_files.append(repo.wjoin(f))
3233 else:
3233 else:
3234 repo.dirstate.drop(f)
3234 repo.dirstate.drop(f)
3235 for f in actions['remove'][0]:
3235 for f in actions['remove'][0]:
3236 audit_path(f)
3236 audit_path(f)
3237 if interactive:
3237 if interactive:
3238 choice = repo.ui.promptchoice(
3238 choice = repo.ui.promptchoice(
3239 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3239 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3240 if choice == 0:
3240 if choice == 0:
3241 doremove(f)
3241 doremove(f)
3242 else:
3242 else:
3243 excluded_files.append(repo.wjoin(f))
3243 excluded_files.append(repo.wjoin(f))
3244 else:
3244 else:
3245 doremove(f)
3245 doremove(f)
3246 for f in actions['drop'][0]:
3246 for f in actions['drop'][0]:
3247 audit_path(f)
3247 audit_path(f)
3248 repo.dirstate.remove(f)
3248 repo.dirstate.remove(f)
3249
3249
3250 normal = None
3250 normal = None
3251 if node == parent:
3251 if node == parent:
3252 # We're reverting to our parent. If possible, we'd like status
3252 # We're reverting to our parent. If possible, we'd like status
3253 # to report the file as clean. We have to use normallookup for
3253 # to report the file as clean. We have to use normallookup for
3254 # merges to avoid losing information about merged/dirty files.
3254 # merges to avoid losing information about merged/dirty files.
3255 if p2 != nullid:
3255 if p2 != nullid:
3256 normal = repo.dirstate.normallookup
3256 normal = repo.dirstate.normallookup
3257 else:
3257 else:
3258 normal = repo.dirstate.normal
3258 normal = repo.dirstate.normal
3259
3259
3260 newlyaddedandmodifiedfiles = set()
3260 newlyaddedandmodifiedfiles = set()
3261 if interactive:
3261 if interactive:
3262 # Prompt the user for changes to revert
3262 # Prompt the user for changes to revert
3263 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3263 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3264 m = scmutil.match(ctx, torevert, matcher_opts)
3264 m = scmutil.match(ctx, torevert, matcher_opts)
3265 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3265 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3266 diffopts.nodates = True
3266 diffopts.nodates = True
3267 diffopts.git = True
3267 diffopts.git = True
3268 operation = 'discard'
3268 operation = 'discard'
3269 reversehunks = True
3269 reversehunks = True
3270 if node != parent:
3270 if node != parent:
3271 operation = 'revert'
3271 operation = 'revert'
3272 reversehunks = repo.ui.configbool('experimental',
3272 reversehunks = repo.ui.configbool('experimental',
3273 'revertalternateinteractivemode',
3273 'revertalternateinteractivemode',
3274 True)
3274 True)
3275 if reversehunks:
3275 if reversehunks:
3276 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3276 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3277 else:
3277 else:
3278 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3278 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3279 originalchunks = patch.parsepatch(diff)
3279 originalchunks = patch.parsepatch(diff)
3280
3280
3281 try:
3281 try:
3282
3282
3283 chunks, opts = recordfilter(repo.ui, originalchunks,
3283 chunks, opts = recordfilter(repo.ui, originalchunks,
3284 operation=operation)
3284 operation=operation)
3285 if reversehunks:
3285 if reversehunks:
3286 chunks = patch.reversehunks(chunks)
3286 chunks = patch.reversehunks(chunks)
3287
3287
3288 except patch.PatchError as err:
3288 except patch.PatchError as err:
3289 raise error.Abort(_('error parsing patch: %s') % err)
3289 raise error.Abort(_('error parsing patch: %s') % err)
3290
3290
3291 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3291 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3292 if tobackup is None:
3292 if tobackup is None:
3293 tobackup = set()
3293 tobackup = set()
3294 # Apply changes
3294 # Apply changes
3295 fp = stringio()
3295 fp = stringio()
3296 for c in chunks:
3296 for c in chunks:
3297 # Create a backup file only if this hunk should be backed up
3297 # Create a backup file only if this hunk should be backed up
3298 if ishunk(c) and c.header.filename() in tobackup:
3298 if ishunk(c) and c.header.filename() in tobackup:
3299 abs = c.header.filename()
3299 abs = c.header.filename()
3300 target = repo.wjoin(abs)
3300 target = repo.wjoin(abs)
3301 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3301 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3302 util.copyfile(target, bakname)
3302 util.copyfile(target, bakname)
3303 tobackup.remove(abs)
3303 tobackup.remove(abs)
3304 c.write(fp)
3304 c.write(fp)
3305 dopatch = fp.tell()
3305 dopatch = fp.tell()
3306 fp.seek(0)
3306 fp.seek(0)
3307 if dopatch:
3307 if dopatch:
3308 try:
3308 try:
3309 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3309 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3310 except patch.PatchError as err:
3310 except patch.PatchError as err:
3311 raise error.Abort(str(err))
3311 raise error.Abort(str(err))
3312 del fp
3312 del fp
3313 else:
3313 else:
3314 for f in actions['revert'][0]:
3314 for f in actions['revert'][0]:
3315 checkout(f)
3315 checkout(f)
3316 if normal:
3316 if normal:
3317 normal(f)
3317 normal(f)
3318
3318
3319 for f in actions['add'][0]:
3319 for f in actions['add'][0]:
3320 # Don't checkout modified files, they are already created by the diff
3320 # Don't checkout modified files, they are already created by the diff
3321 if f not in newlyaddedandmodifiedfiles:
3321 if f not in newlyaddedandmodifiedfiles:
3322 checkout(f)
3322 checkout(f)
3323 repo.dirstate.add(f)
3323 repo.dirstate.add(f)
3324
3324
3325 normal = repo.dirstate.normallookup
3325 normal = repo.dirstate.normallookup
3326 if node == parent and p2 == nullid:
3326 if node == parent and p2 == nullid:
3327 normal = repo.dirstate.normal
3327 normal = repo.dirstate.normal
3328 for f in actions['undelete'][0]:
3328 for f in actions['undelete'][0]:
3329 checkout(f)
3329 checkout(f)
3330 normal(f)
3330 normal(f)
3331
3331
3332 copied = copies.pathcopies(repo[parent], ctx)
3332 copied = copies.pathcopies(repo[parent], ctx)
3333
3333
3334 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3334 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3335 if f in copied:
3335 if f in copied:
3336 repo.dirstate.copy(copied[f], f)
3336 repo.dirstate.copy(copied[f], f)
3337
3337
3338 command = registrar.command
3338 class command(registrar.command):
3339 def _doregister(self, func, name, *args, **kwargs):
3340 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3341 return super(command, self)._doregister(func, name, *args, **kwargs)
3339
3342
3340 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3343 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3341 # commands.outgoing. "missing" is "missing" of the result of
3344 # commands.outgoing. "missing" is "missing" of the result of
3342 # "findcommonoutgoing()"
3345 # "findcommonoutgoing()"
3343 outgoinghooks = util.hooks()
3346 outgoinghooks = util.hooks()
3344
3347
3345 # a list of (ui, repo) functions called by commands.summary
3348 # a list of (ui, repo) functions called by commands.summary
3346 summaryhooks = util.hooks()
3349 summaryhooks = util.hooks()
3347
3350
3348 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3351 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3349 #
3352 #
3350 # functions should return tuple of booleans below, if 'changes' is None:
3353 # functions should return tuple of booleans below, if 'changes' is None:
3351 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3354 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3352 #
3355 #
3353 # otherwise, 'changes' is a tuple of tuples below:
3356 # otherwise, 'changes' is a tuple of tuples below:
3354 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3357 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3355 # - (desturl, destbranch, destpeer, outgoing)
3358 # - (desturl, destbranch, destpeer, outgoing)
3356 summaryremotehooks = util.hooks()
3359 summaryremotehooks = util.hooks()
3357
3360
3358 # A list of state files kept by multistep operations like graft.
3361 # A list of state files kept by multistep operations like graft.
3359 # Since graft cannot be aborted, it is considered 'clearable' by update.
3362 # Since graft cannot be aborted, it is considered 'clearable' by update.
3360 # note: bisect is intentionally excluded
3363 # note: bisect is intentionally excluded
3361 # (state file, clearable, allowcommit, error, hint)
3364 # (state file, clearable, allowcommit, error, hint)
3362 unfinishedstates = [
3365 unfinishedstates = [
3363 ('graftstate', True, False, _('graft in progress'),
3366 ('graftstate', True, False, _('graft in progress'),
3364 _("use 'hg graft --continue' or 'hg update' to abort")),
3367 _("use 'hg graft --continue' or 'hg update' to abort")),
3365 ('updatestate', True, False, _('last update was interrupted'),
3368 ('updatestate', True, False, _('last update was interrupted'),
3366 _("use 'hg update' to get a consistent checkout"))
3369 _("use 'hg update' to get a consistent checkout"))
3367 ]
3370 ]
3368
3371
3369 def checkunfinished(repo, commit=False):
3372 def checkunfinished(repo, commit=False):
3370 '''Look for an unfinished multistep operation, like graft, and abort
3373 '''Look for an unfinished multistep operation, like graft, and abort
3371 if found. It's probably good to check this right before
3374 if found. It's probably good to check this right before
3372 bailifchanged().
3375 bailifchanged().
3373 '''
3376 '''
3374 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3377 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3375 if commit and allowcommit:
3378 if commit and allowcommit:
3376 continue
3379 continue
3377 if repo.vfs.exists(f):
3380 if repo.vfs.exists(f):
3378 raise error.Abort(msg, hint=hint)
3381 raise error.Abort(msg, hint=hint)
3379
3382
3380 def clearunfinished(repo):
3383 def clearunfinished(repo):
3381 '''Check for unfinished operations (as above), and clear the ones
3384 '''Check for unfinished operations (as above), and clear the ones
3382 that are clearable.
3385 that are clearable.
3383 '''
3386 '''
3384 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3387 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3385 if not clearable and repo.vfs.exists(f):
3388 if not clearable and repo.vfs.exists(f):
3386 raise error.Abort(msg, hint=hint)
3389 raise error.Abort(msg, hint=hint)
3387 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3390 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3388 if clearable and repo.vfs.exists(f):
3391 if clearable and repo.vfs.exists(f):
3389 util.unlink(repo.vfs.join(f))
3392 util.unlink(repo.vfs.join(f))
3390
3393
3391 afterresolvedstates = [
3394 afterresolvedstates = [
3392 ('graftstate',
3395 ('graftstate',
3393 _('hg graft --continue')),
3396 _('hg graft --continue')),
3394 ]
3397 ]
3395
3398
3396 def howtocontinue(repo):
3399 def howtocontinue(repo):
3397 '''Check for an unfinished operation and return the command to finish
3400 '''Check for an unfinished operation and return the command to finish
3398 it.
3401 it.
3399
3402
3400 afterresolvedstates tuples define a .hg/{file} and the corresponding
3403 afterresolvedstates tuples define a .hg/{file} and the corresponding
3401 command needed to finish it.
3404 command needed to finish it.
3402
3405
3403 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3406 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3404 a boolean.
3407 a boolean.
3405 '''
3408 '''
3406 contmsg = _("continue: %s")
3409 contmsg = _("continue: %s")
3407 for f, msg in afterresolvedstates:
3410 for f, msg in afterresolvedstates:
3408 if repo.vfs.exists(f):
3411 if repo.vfs.exists(f):
3409 return contmsg % msg, True
3412 return contmsg % msg, True
3410 workingctx = repo[None]
3413 workingctx = repo[None]
3411 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3414 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3412 for s in workingctx.substate)
3415 for s in workingctx.substate)
3413 if dirty:
3416 if dirty:
3414 return contmsg % _("hg commit"), False
3417 return contmsg % _("hg commit"), False
3415 return None, None
3418 return None, None
3416
3419
3417 def checkafterresolved(repo):
3420 def checkafterresolved(repo):
3418 '''Inform the user about the next action after completing hg resolve
3421 '''Inform the user about the next action after completing hg resolve
3419
3422
3420 If there's a matching afterresolvedstates, howtocontinue will yield
3423 If there's a matching afterresolvedstates, howtocontinue will yield
3421 repo.ui.warn as the reporter.
3424 repo.ui.warn as the reporter.
3422
3425
3423 Otherwise, it will yield repo.ui.note.
3426 Otherwise, it will yield repo.ui.note.
3424 '''
3427 '''
3425 msg, warning = howtocontinue(repo)
3428 msg, warning = howtocontinue(repo)
3426 if msg is not None:
3429 if msg is not None:
3427 if warning:
3430 if warning:
3428 repo.ui.warn("%s\n" % msg)
3431 repo.ui.warn("%s\n" % msg)
3429 else:
3432 else:
3430 repo.ui.note("%s\n" % msg)
3433 repo.ui.note("%s\n" % msg)
3431
3434
3432 def wrongtooltocontinue(repo, task):
3435 def wrongtooltocontinue(repo, task):
3433 '''Raise an abort suggesting how to properly continue if there is an
3436 '''Raise an abort suggesting how to properly continue if there is an
3434 active task.
3437 active task.
3435
3438
3436 Uses howtocontinue() to find the active task.
3439 Uses howtocontinue() to find the active task.
3437
3440
3438 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3441 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3439 a hint.
3442 a hint.
3440 '''
3443 '''
3441 after = howtocontinue(repo)
3444 after = howtocontinue(repo)
3442 hint = None
3445 hint = None
3443 if after[1]:
3446 if after[1]:
3444 hint = after[0]
3447 hint = after[0]
3445 raise error.Abort(_('no %s in progress') % task, hint=hint)
3448 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,573 +1,576 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for 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 imp
10 import imp
11 import inspect
11 import inspect
12 import os
12 import os
13
13
14 from .i18n import (
14 from .i18n import (
15 _,
15 _,
16 gettext,
16 gettext,
17 )
17 )
18
18
19 from . import (
19 from . import (
20 cmdutil,
20 cmdutil,
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26
26
27 _extensions = {}
27 _extensions = {}
28 _disabledextensions = {}
28 _disabledextensions = {}
29 _aftercallbacks = {}
29 _aftercallbacks = {}
30 _order = []
30 _order = []
31 _builtin = {'hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
31 _builtin = {'hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
32 'inotify', 'hgcia'}
32 'inotify', 'hgcia'}
33
33
34 def extensions(ui=None):
34 def extensions(ui=None):
35 if ui:
35 if ui:
36 def enabled(name):
36 def enabled(name):
37 for format in ['%s', 'hgext.%s']:
37 for format in ['%s', 'hgext.%s']:
38 conf = ui.config('extensions', format % name)
38 conf = ui.config('extensions', format % name)
39 if conf is not None and not conf.startswith('!'):
39 if conf is not None and not conf.startswith('!'):
40 return True
40 return True
41 else:
41 else:
42 enabled = lambda name: True
42 enabled = lambda name: True
43 for name in _order:
43 for name in _order:
44 module = _extensions[name]
44 module = _extensions[name]
45 if module and enabled(name):
45 if module and enabled(name):
46 yield name, module
46 yield name, module
47
47
48 def find(name):
48 def find(name):
49 '''return module with given extension name'''
49 '''return module with given extension name'''
50 mod = None
50 mod = None
51 try:
51 try:
52 mod = _extensions[name]
52 mod = _extensions[name]
53 except KeyError:
53 except KeyError:
54 for k, v in _extensions.iteritems():
54 for k, v in _extensions.iteritems():
55 if k.endswith('.' + name) or k.endswith('/' + name):
55 if k.endswith('.' + name) or k.endswith('/' + name):
56 mod = v
56 mod = v
57 break
57 break
58 if not mod:
58 if not mod:
59 raise KeyError(name)
59 raise KeyError(name)
60 return mod
60 return mod
61
61
62 def loadpath(path, module_name):
62 def loadpath(path, module_name):
63 module_name = module_name.replace('.', '_')
63 module_name = module_name.replace('.', '_')
64 path = util.normpath(util.expandpath(path))
64 path = util.normpath(util.expandpath(path))
65 module_name = pycompat.fsdecode(module_name)
65 module_name = pycompat.fsdecode(module_name)
66 path = pycompat.fsdecode(path)
66 path = pycompat.fsdecode(path)
67 if os.path.isdir(path):
67 if os.path.isdir(path):
68 # module/__init__.py style
68 # module/__init__.py style
69 d, f = os.path.split(path)
69 d, f = os.path.split(path)
70 fd, fpath, desc = imp.find_module(f, [d])
70 fd, fpath, desc = imp.find_module(f, [d])
71 return imp.load_module(module_name, fd, fpath, desc)
71 return imp.load_module(module_name, fd, fpath, desc)
72 else:
72 else:
73 try:
73 try:
74 return imp.load_source(module_name, path)
74 return imp.load_source(module_name, path)
75 except IOError as exc:
75 except IOError as exc:
76 if not exc.filename:
76 if not exc.filename:
77 exc.filename = path # python does not fill this
77 exc.filename = path # python does not fill this
78 raise
78 raise
79
79
80 def _importh(name):
80 def _importh(name):
81 """import and return the <name> module"""
81 """import and return the <name> module"""
82 mod = __import__(pycompat.sysstr(name))
82 mod = __import__(pycompat.sysstr(name))
83 components = name.split('.')
83 components = name.split('.')
84 for comp in components[1:]:
84 for comp in components[1:]:
85 mod = getattr(mod, comp)
85 mod = getattr(mod, comp)
86 return mod
86 return mod
87
87
88 def _importext(name, path=None, reportfunc=None):
88 def _importext(name, path=None, reportfunc=None):
89 if path:
89 if path:
90 # the module will be loaded in sys.modules
90 # the module will be loaded in sys.modules
91 # choose an unique name so that it doesn't
91 # choose an unique name so that it doesn't
92 # conflicts with other modules
92 # conflicts with other modules
93 mod = loadpath(path, 'hgext.%s' % name)
93 mod = loadpath(path, 'hgext.%s' % name)
94 else:
94 else:
95 try:
95 try:
96 mod = _importh("hgext.%s" % name)
96 mod = _importh("hgext.%s" % name)
97 except ImportError as err:
97 except ImportError as err:
98 if reportfunc:
98 if reportfunc:
99 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
99 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
100 try:
100 try:
101 mod = _importh("hgext3rd.%s" % name)
101 mod = _importh("hgext3rd.%s" % name)
102 except ImportError as err:
102 except ImportError as err:
103 if reportfunc:
103 if reportfunc:
104 reportfunc(err, "hgext3rd.%s" % name, name)
104 reportfunc(err, "hgext3rd.%s" % name, name)
105 mod = _importh(name)
105 mod = _importh(name)
106 return mod
106 return mod
107
107
108 def _forbytes(inst):
108 def _forbytes(inst):
109 """Portably format an import error into a form suitable for
109 """Portably format an import error into a form suitable for
110 %-formatting into bytestrings."""
110 %-formatting into bytestrings."""
111 return encoding.strtolocal(str(inst))
111 return encoding.strtolocal(str(inst))
112
112
113 def _reportimporterror(ui, err, failed, next):
113 def _reportimporterror(ui, err, failed, next):
114 # note: this ui.debug happens before --debug is processed,
114 # note: this ui.debug happens before --debug is processed,
115 # Use --config ui.debug=1 to see them.
115 # Use --config ui.debug=1 to see them.
116 ui.debug('could not import %s (%s): trying %s\n'
116 ui.debug('could not import %s (%s): trying %s\n'
117 % (failed, _forbytes(err), next))
117 % (failed, _forbytes(err), next))
118 if ui.debugflag:
118 if ui.debugflag:
119 ui.traceback()
119 ui.traceback()
120
120
121 # attributes set by registrar.command
121 # attributes set by registrar.command
122 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
122 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
123
123
124 def _validatecmdtable(cmdtable):
124 def _validatecmdtable(ui, cmdtable):
125 """Check if extension commands have required attributes"""
125 """Check if extension commands have required attributes"""
126 for c, e in cmdtable.iteritems():
126 for c, e in cmdtable.iteritems():
127 f = e[0]
127 f = e[0]
128 if getattr(f, '_deprecatedregistrar', False):
129 ui.deprecwarn("cmdutil.command is deprecated, use "
130 "registrar.command to register '%s'" % c, '4.6')
128 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
131 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
129 if not missing:
132 if not missing:
130 continue
133 continue
131 raise error.ProgrammingError(
134 raise error.ProgrammingError(
132 'missing attributes: %s' % ', '.join(missing),
135 'missing attributes: %s' % ', '.join(missing),
133 hint="use @command decorator to register '%s'" % c)
136 hint="use @command decorator to register '%s'" % c)
134
137
135 def load(ui, name, path):
138 def load(ui, name, path):
136 if name.startswith('hgext.') or name.startswith('hgext/'):
139 if name.startswith('hgext.') or name.startswith('hgext/'):
137 shortname = name[6:]
140 shortname = name[6:]
138 else:
141 else:
139 shortname = name
142 shortname = name
140 if shortname in _builtin:
143 if shortname in _builtin:
141 return None
144 return None
142 if shortname in _extensions:
145 if shortname in _extensions:
143 return _extensions[shortname]
146 return _extensions[shortname]
144 _extensions[shortname] = None
147 _extensions[shortname] = None
145 mod = _importext(name, path, bind(_reportimporterror, ui))
148 mod = _importext(name, path, bind(_reportimporterror, ui))
146
149
147 # Before we do anything with the extension, check against minimum stated
150 # Before we do anything with the extension, check against minimum stated
148 # compatibility. This gives extension authors a mechanism to have their
151 # compatibility. This gives extension authors a mechanism to have their
149 # extensions short circuit when loaded with a known incompatible version
152 # extensions short circuit when loaded with a known incompatible version
150 # of Mercurial.
153 # of Mercurial.
151 minver = getattr(mod, 'minimumhgversion', None)
154 minver = getattr(mod, 'minimumhgversion', None)
152 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
155 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
153 ui.warn(_('(third party extension %s requires version %s or newer '
156 ui.warn(_('(third party extension %s requires version %s or newer '
154 'of Mercurial; disabling)\n') % (shortname, minver))
157 'of Mercurial; disabling)\n') % (shortname, minver))
155 return
158 return
156 _validatecmdtable(getattr(mod, 'cmdtable', {}))
159 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
157
160
158 _extensions[shortname] = mod
161 _extensions[shortname] = mod
159 _order.append(shortname)
162 _order.append(shortname)
160 for fn in _aftercallbacks.get(shortname, []):
163 for fn in _aftercallbacks.get(shortname, []):
161 fn(loaded=True)
164 fn(loaded=True)
162 return mod
165 return mod
163
166
164 def _runuisetup(name, ui):
167 def _runuisetup(name, ui):
165 uisetup = getattr(_extensions[name], 'uisetup', None)
168 uisetup = getattr(_extensions[name], 'uisetup', None)
166 if uisetup:
169 if uisetup:
167 uisetup(ui)
170 uisetup(ui)
168
171
169 def _runextsetup(name, ui):
172 def _runextsetup(name, ui):
170 extsetup = getattr(_extensions[name], 'extsetup', None)
173 extsetup = getattr(_extensions[name], 'extsetup', None)
171 if extsetup:
174 if extsetup:
172 try:
175 try:
173 extsetup(ui)
176 extsetup(ui)
174 except TypeError:
177 except TypeError:
175 if inspect.getargspec(extsetup).args:
178 if inspect.getargspec(extsetup).args:
176 raise
179 raise
177 extsetup() # old extsetup with no ui argument
180 extsetup() # old extsetup with no ui argument
178
181
179 def loadall(ui):
182 def loadall(ui):
180 result = ui.configitems("extensions")
183 result = ui.configitems("extensions")
181 newindex = len(_order)
184 newindex = len(_order)
182 for (name, path) in result:
185 for (name, path) in result:
183 if path:
186 if path:
184 if path[0:1] == '!':
187 if path[0:1] == '!':
185 _disabledextensions[name] = path[1:]
188 _disabledextensions[name] = path[1:]
186 continue
189 continue
187 try:
190 try:
188 load(ui, name, path)
191 load(ui, name, path)
189 except Exception as inst:
192 except Exception as inst:
190 msg = _forbytes(inst)
193 msg = _forbytes(inst)
191 if path:
194 if path:
192 ui.warn(_("*** failed to import extension %s from %s: %s\n")
195 ui.warn(_("*** failed to import extension %s from %s: %s\n")
193 % (name, path, msg))
196 % (name, path, msg))
194 else:
197 else:
195 ui.warn(_("*** failed to import extension %s: %s\n")
198 ui.warn(_("*** failed to import extension %s: %s\n")
196 % (name, msg))
199 % (name, msg))
197 if isinstance(inst, error.Hint) and inst.hint:
200 if isinstance(inst, error.Hint) and inst.hint:
198 ui.warn(_("*** (%s)\n") % inst.hint)
201 ui.warn(_("*** (%s)\n") % inst.hint)
199 ui.traceback()
202 ui.traceback()
200
203
201 for name in _order[newindex:]:
204 for name in _order[newindex:]:
202 _runuisetup(name, ui)
205 _runuisetup(name, ui)
203
206
204 for name in _order[newindex:]:
207 for name in _order[newindex:]:
205 _runextsetup(name, ui)
208 _runextsetup(name, ui)
206
209
207 # Call aftercallbacks that were never met.
210 # Call aftercallbacks that were never met.
208 for shortname in _aftercallbacks:
211 for shortname in _aftercallbacks:
209 if shortname in _extensions:
212 if shortname in _extensions:
210 continue
213 continue
211
214
212 for fn in _aftercallbacks[shortname]:
215 for fn in _aftercallbacks[shortname]:
213 fn(loaded=False)
216 fn(loaded=False)
214
217
215 # loadall() is called multiple times and lingering _aftercallbacks
218 # loadall() is called multiple times and lingering _aftercallbacks
216 # entries could result in double execution. See issue4646.
219 # entries could result in double execution. See issue4646.
217 _aftercallbacks.clear()
220 _aftercallbacks.clear()
218
221
219 def afterloaded(extension, callback):
222 def afterloaded(extension, callback):
220 '''Run the specified function after a named extension is loaded.
223 '''Run the specified function after a named extension is loaded.
221
224
222 If the named extension is already loaded, the callback will be called
225 If the named extension is already loaded, the callback will be called
223 immediately.
226 immediately.
224
227
225 If the named extension never loads, the callback will be called after
228 If the named extension never loads, the callback will be called after
226 all extensions have been loaded.
229 all extensions have been loaded.
227
230
228 The callback receives the named argument ``loaded``, which is a boolean
231 The callback receives the named argument ``loaded``, which is a boolean
229 indicating whether the dependent extension actually loaded.
232 indicating whether the dependent extension actually loaded.
230 '''
233 '''
231
234
232 if extension in _extensions:
235 if extension in _extensions:
233 callback(loaded=True)
236 callback(loaded=True)
234 else:
237 else:
235 _aftercallbacks.setdefault(extension, []).append(callback)
238 _aftercallbacks.setdefault(extension, []).append(callback)
236
239
237 def bind(func, *args):
240 def bind(func, *args):
238 '''Partial function application
241 '''Partial function application
239
242
240 Returns a new function that is the partial application of args and kwargs
243 Returns a new function that is the partial application of args and kwargs
241 to func. For example,
244 to func. For example,
242
245
243 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
246 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
244 assert callable(func)
247 assert callable(func)
245 def closure(*a, **kw):
248 def closure(*a, **kw):
246 return func(*(args + a), **kw)
249 return func(*(args + a), **kw)
247 return closure
250 return closure
248
251
249 def _updatewrapper(wrap, origfn, unboundwrapper):
252 def _updatewrapper(wrap, origfn, unboundwrapper):
250 '''Copy and add some useful attributes to wrapper'''
253 '''Copy and add some useful attributes to wrapper'''
251 wrap.__module__ = getattr(origfn, '__module__')
254 wrap.__module__ = getattr(origfn, '__module__')
252 wrap.__doc__ = getattr(origfn, '__doc__')
255 wrap.__doc__ = getattr(origfn, '__doc__')
253 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
256 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
254 wrap._origfunc = origfn
257 wrap._origfunc = origfn
255 wrap._unboundwrapper = unboundwrapper
258 wrap._unboundwrapper = unboundwrapper
256
259
257 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
260 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
258 '''Wrap the command named `command' in table
261 '''Wrap the command named `command' in table
259
262
260 Replace command in the command table with wrapper. The wrapped command will
263 Replace command in the command table with wrapper. The wrapped command will
261 be inserted into the command table specified by the table argument.
264 be inserted into the command table specified by the table argument.
262
265
263 The wrapper will be called like
266 The wrapper will be called like
264
267
265 wrapper(orig, *args, **kwargs)
268 wrapper(orig, *args, **kwargs)
266
269
267 where orig is the original (wrapped) function, and *args, **kwargs
270 where orig is the original (wrapped) function, and *args, **kwargs
268 are the arguments passed to it.
271 are the arguments passed to it.
269
272
270 Optionally append to the command synopsis and docstring, used for help.
273 Optionally append to the command synopsis and docstring, used for help.
271 For example, if your extension wraps the ``bookmarks`` command to add the
274 For example, if your extension wraps the ``bookmarks`` command to add the
272 flags ``--remote`` and ``--all`` you might call this function like so:
275 flags ``--remote`` and ``--all`` you might call this function like so:
273
276
274 synopsis = ' [-a] [--remote]'
277 synopsis = ' [-a] [--remote]'
275 docstring = """
278 docstring = """
276
279
277 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
280 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
278 flags to the bookmarks command. Either flag will show the remote bookmarks
281 flags to the bookmarks command. Either flag will show the remote bookmarks
279 known to the repository; ``--remote`` will also suppress the output of the
282 known to the repository; ``--remote`` will also suppress the output of the
280 local bookmarks.
283 local bookmarks.
281 """
284 """
282
285
283 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
286 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
284 synopsis, docstring)
287 synopsis, docstring)
285 '''
288 '''
286 assert callable(wrapper)
289 assert callable(wrapper)
287 aliases, entry = cmdutil.findcmd(command, table)
290 aliases, entry = cmdutil.findcmd(command, table)
288 for alias, e in table.iteritems():
291 for alias, e in table.iteritems():
289 if e is entry:
292 if e is entry:
290 key = alias
293 key = alias
291 break
294 break
292
295
293 origfn = entry[0]
296 origfn = entry[0]
294 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
297 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
295 _updatewrapper(wrap, origfn, wrapper)
298 _updatewrapper(wrap, origfn, wrapper)
296 if docstring is not None:
299 if docstring is not None:
297 wrap.__doc__ += docstring
300 wrap.__doc__ += docstring
298
301
299 newentry = list(entry)
302 newentry = list(entry)
300 newentry[0] = wrap
303 newentry[0] = wrap
301 if synopsis is not None:
304 if synopsis is not None:
302 newentry[2] += synopsis
305 newentry[2] += synopsis
303 table[key] = tuple(newentry)
306 table[key] = tuple(newentry)
304 return entry
307 return entry
305
308
306 def wrapfunction(container, funcname, wrapper):
309 def wrapfunction(container, funcname, wrapper):
307 '''Wrap the function named funcname in container
310 '''Wrap the function named funcname in container
308
311
309 Replace the funcname member in the given container with the specified
312 Replace the funcname member in the given container with the specified
310 wrapper. The container is typically a module, class, or instance.
313 wrapper. The container is typically a module, class, or instance.
311
314
312 The wrapper will be called like
315 The wrapper will be called like
313
316
314 wrapper(orig, *args, **kwargs)
317 wrapper(orig, *args, **kwargs)
315
318
316 where orig is the original (wrapped) function, and *args, **kwargs
319 where orig is the original (wrapped) function, and *args, **kwargs
317 are the arguments passed to it.
320 are the arguments passed to it.
318
321
319 Wrapping methods of the repository object is not recommended since
322 Wrapping methods of the repository object is not recommended since
320 it conflicts with extensions that extend the repository by
323 it conflicts with extensions that extend the repository by
321 subclassing. All extensions that need to extend methods of
324 subclassing. All extensions that need to extend methods of
322 localrepository should use this subclassing trick: namely,
325 localrepository should use this subclassing trick: namely,
323 reposetup() should look like
326 reposetup() should look like
324
327
325 def reposetup(ui, repo):
328 def reposetup(ui, repo):
326 class myrepo(repo.__class__):
329 class myrepo(repo.__class__):
327 def whatever(self, *args, **kwargs):
330 def whatever(self, *args, **kwargs):
328 [...extension stuff...]
331 [...extension stuff...]
329 super(myrepo, self).whatever(*args, **kwargs)
332 super(myrepo, self).whatever(*args, **kwargs)
330 [...extension stuff...]
333 [...extension stuff...]
331
334
332 repo.__class__ = myrepo
335 repo.__class__ = myrepo
333
336
334 In general, combining wrapfunction() with subclassing does not
337 In general, combining wrapfunction() with subclassing does not
335 work. Since you cannot control what other extensions are loaded by
338 work. Since you cannot control what other extensions are loaded by
336 your end users, you should play nicely with others by using the
339 your end users, you should play nicely with others by using the
337 subclass trick.
340 subclass trick.
338 '''
341 '''
339 assert callable(wrapper)
342 assert callable(wrapper)
340
343
341 origfn = getattr(container, funcname)
344 origfn = getattr(container, funcname)
342 assert callable(origfn)
345 assert callable(origfn)
343 wrap = bind(wrapper, origfn)
346 wrap = bind(wrapper, origfn)
344 _updatewrapper(wrap, origfn, wrapper)
347 _updatewrapper(wrap, origfn, wrapper)
345 setattr(container, funcname, wrap)
348 setattr(container, funcname, wrap)
346 return origfn
349 return origfn
347
350
348 def unwrapfunction(container, funcname, wrapper=None):
351 def unwrapfunction(container, funcname, wrapper=None):
349 '''undo wrapfunction
352 '''undo wrapfunction
350
353
351 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
354 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
352 from the chain of wrappers.
355 from the chain of wrappers.
353
356
354 Return the removed wrapper.
357 Return the removed wrapper.
355 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
358 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
356 wrapper is not None but is not found in the wrapper chain.
359 wrapper is not None but is not found in the wrapper chain.
357 '''
360 '''
358 chain = getwrapperchain(container, funcname)
361 chain = getwrapperchain(container, funcname)
359 origfn = chain.pop()
362 origfn = chain.pop()
360 if wrapper is None:
363 if wrapper is None:
361 wrapper = chain[0]
364 wrapper = chain[0]
362 chain.remove(wrapper)
365 chain.remove(wrapper)
363 setattr(container, funcname, origfn)
366 setattr(container, funcname, origfn)
364 for w in reversed(chain):
367 for w in reversed(chain):
365 wrapfunction(container, funcname, w)
368 wrapfunction(container, funcname, w)
366 return wrapper
369 return wrapper
367
370
368 def getwrapperchain(container, funcname):
371 def getwrapperchain(container, funcname):
369 '''get a chain of wrappers of a function
372 '''get a chain of wrappers of a function
370
373
371 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
374 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
372
375
373 The wrapper functions are the ones passed to wrapfunction, whose first
376 The wrapper functions are the ones passed to wrapfunction, whose first
374 argument is origfunc.
377 argument is origfunc.
375 '''
378 '''
376 result = []
379 result = []
377 fn = getattr(container, funcname)
380 fn = getattr(container, funcname)
378 while fn:
381 while fn:
379 assert callable(fn)
382 assert callable(fn)
380 result.append(getattr(fn, '_unboundwrapper', fn))
383 result.append(getattr(fn, '_unboundwrapper', fn))
381 fn = getattr(fn, '_origfunc', None)
384 fn = getattr(fn, '_origfunc', None)
382 return result
385 return result
383
386
384 def _disabledpaths(strip_init=False):
387 def _disabledpaths(strip_init=False):
385 '''find paths of disabled extensions. returns a dict of {name: path}
388 '''find paths of disabled extensions. returns a dict of {name: path}
386 removes /__init__.py from packages if strip_init is True'''
389 removes /__init__.py from packages if strip_init is True'''
387 import hgext
390 import hgext
388 extpath = os.path.dirname(
391 extpath = os.path.dirname(
389 os.path.abspath(pycompat.fsencode(hgext.__file__)))
392 os.path.abspath(pycompat.fsencode(hgext.__file__)))
390 try: # might not be a filesystem path
393 try: # might not be a filesystem path
391 files = os.listdir(extpath)
394 files = os.listdir(extpath)
392 except OSError:
395 except OSError:
393 return {}
396 return {}
394
397
395 exts = {}
398 exts = {}
396 for e in files:
399 for e in files:
397 if e.endswith('.py'):
400 if e.endswith('.py'):
398 name = e.rsplit('.', 1)[0]
401 name = e.rsplit('.', 1)[0]
399 path = os.path.join(extpath, e)
402 path = os.path.join(extpath, e)
400 else:
403 else:
401 name = e
404 name = e
402 path = os.path.join(extpath, e, '__init__.py')
405 path = os.path.join(extpath, e, '__init__.py')
403 if not os.path.exists(path):
406 if not os.path.exists(path):
404 continue
407 continue
405 if strip_init:
408 if strip_init:
406 path = os.path.dirname(path)
409 path = os.path.dirname(path)
407 if name in exts or name in _order or name == '__init__':
410 if name in exts or name in _order or name == '__init__':
408 continue
411 continue
409 exts[name] = path
412 exts[name] = path
410 exts.update(_disabledextensions)
413 exts.update(_disabledextensions)
411 return exts
414 return exts
412
415
413 def _moduledoc(file):
416 def _moduledoc(file):
414 '''return the top-level python documentation for the given file
417 '''return the top-level python documentation for the given file
415
418
416 Loosely inspired by pydoc.source_synopsis(), but rewritten to
419 Loosely inspired by pydoc.source_synopsis(), but rewritten to
417 handle triple quotes and to return the whole text instead of just
420 handle triple quotes and to return the whole text instead of just
418 the synopsis'''
421 the synopsis'''
419 result = []
422 result = []
420
423
421 line = file.readline()
424 line = file.readline()
422 while line[:1] == '#' or not line.strip():
425 while line[:1] == '#' or not line.strip():
423 line = file.readline()
426 line = file.readline()
424 if not line:
427 if not line:
425 break
428 break
426
429
427 start = line[:3]
430 start = line[:3]
428 if start == '"""' or start == "'''":
431 if start == '"""' or start == "'''":
429 line = line[3:]
432 line = line[3:]
430 while line:
433 while line:
431 if line.rstrip().endswith(start):
434 if line.rstrip().endswith(start):
432 line = line.split(start)[0]
435 line = line.split(start)[0]
433 if line:
436 if line:
434 result.append(line)
437 result.append(line)
435 break
438 break
436 elif not line:
439 elif not line:
437 return None # unmatched delimiter
440 return None # unmatched delimiter
438 result.append(line)
441 result.append(line)
439 line = file.readline()
442 line = file.readline()
440 else:
443 else:
441 return None
444 return None
442
445
443 return ''.join(result)
446 return ''.join(result)
444
447
445 def _disabledhelp(path):
448 def _disabledhelp(path):
446 '''retrieve help synopsis of a disabled extension (without importing)'''
449 '''retrieve help synopsis of a disabled extension (without importing)'''
447 try:
450 try:
448 file = open(path)
451 file = open(path)
449 except IOError:
452 except IOError:
450 return
453 return
451 else:
454 else:
452 doc = _moduledoc(file)
455 doc = _moduledoc(file)
453 file.close()
456 file.close()
454
457
455 if doc: # extracting localized synopsis
458 if doc: # extracting localized synopsis
456 return gettext(doc)
459 return gettext(doc)
457 else:
460 else:
458 return _('(no help text available)')
461 return _('(no help text available)')
459
462
460 def disabled():
463 def disabled():
461 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
464 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
462 try:
465 try:
463 from hgext import __index__
466 from hgext import __index__
464 return dict((name, gettext(desc))
467 return dict((name, gettext(desc))
465 for name, desc in __index__.docs.iteritems()
468 for name, desc in __index__.docs.iteritems()
466 if name not in _order)
469 if name not in _order)
467 except (ImportError, AttributeError):
470 except (ImportError, AttributeError):
468 pass
471 pass
469
472
470 paths = _disabledpaths()
473 paths = _disabledpaths()
471 if not paths:
474 if not paths:
472 return {}
475 return {}
473
476
474 exts = {}
477 exts = {}
475 for name, path in paths.iteritems():
478 for name, path in paths.iteritems():
476 doc = _disabledhelp(path)
479 doc = _disabledhelp(path)
477 if doc:
480 if doc:
478 exts[name] = doc.splitlines()[0]
481 exts[name] = doc.splitlines()[0]
479
482
480 return exts
483 return exts
481
484
482 def disabledext(name):
485 def disabledext(name):
483 '''find a specific disabled extension from hgext. returns desc'''
486 '''find a specific disabled extension from hgext. returns desc'''
484 try:
487 try:
485 from hgext import __index__
488 from hgext import __index__
486 if name in _order: # enabled
489 if name in _order: # enabled
487 return
490 return
488 else:
491 else:
489 return gettext(__index__.docs.get(name))
492 return gettext(__index__.docs.get(name))
490 except (ImportError, AttributeError):
493 except (ImportError, AttributeError):
491 pass
494 pass
492
495
493 paths = _disabledpaths()
496 paths = _disabledpaths()
494 if name in paths:
497 if name in paths:
495 return _disabledhelp(paths[name])
498 return _disabledhelp(paths[name])
496
499
497 def disabledcmd(ui, cmd, strict=False):
500 def disabledcmd(ui, cmd, strict=False):
498 '''import disabled extensions until cmd is found.
501 '''import disabled extensions until cmd is found.
499 returns (cmdname, extname, module)'''
502 returns (cmdname, extname, module)'''
500
503
501 paths = _disabledpaths(strip_init=True)
504 paths = _disabledpaths(strip_init=True)
502 if not paths:
505 if not paths:
503 raise error.UnknownCommand(cmd)
506 raise error.UnknownCommand(cmd)
504
507
505 def findcmd(cmd, name, path):
508 def findcmd(cmd, name, path):
506 try:
509 try:
507 mod = loadpath(path, 'hgext.%s' % name)
510 mod = loadpath(path, 'hgext.%s' % name)
508 except Exception:
511 except Exception:
509 return
512 return
510 try:
513 try:
511 aliases, entry = cmdutil.findcmd(cmd,
514 aliases, entry = cmdutil.findcmd(cmd,
512 getattr(mod, 'cmdtable', {}), strict)
515 getattr(mod, 'cmdtable', {}), strict)
513 except (error.AmbiguousCommand, error.UnknownCommand):
516 except (error.AmbiguousCommand, error.UnknownCommand):
514 return
517 return
515 except Exception:
518 except Exception:
516 ui.warn(_('warning: error finding commands in %s\n') % path)
519 ui.warn(_('warning: error finding commands in %s\n') % path)
517 ui.traceback()
520 ui.traceback()
518 return
521 return
519 for c in aliases:
522 for c in aliases:
520 if c.startswith(cmd):
523 if c.startswith(cmd):
521 cmd = c
524 cmd = c
522 break
525 break
523 else:
526 else:
524 cmd = aliases[0]
527 cmd = aliases[0]
525 return (cmd, name, mod)
528 return (cmd, name, mod)
526
529
527 ext = None
530 ext = None
528 # first, search for an extension with the same name as the command
531 # first, search for an extension with the same name as the command
529 path = paths.pop(cmd, None)
532 path = paths.pop(cmd, None)
530 if path:
533 if path:
531 ext = findcmd(cmd, cmd, path)
534 ext = findcmd(cmd, cmd, path)
532 if not ext:
535 if not ext:
533 # otherwise, interrogate each extension until there's a match
536 # otherwise, interrogate each extension until there's a match
534 for name, path in paths.iteritems():
537 for name, path in paths.iteritems():
535 ext = findcmd(cmd, name, path)
538 ext = findcmd(cmd, name, path)
536 if ext:
539 if ext:
537 break
540 break
538 if ext and 'DEPRECATED' not in ext.__doc__:
541 if ext and 'DEPRECATED' not in ext.__doc__:
539 return ext
542 return ext
540
543
541 raise error.UnknownCommand(cmd)
544 raise error.UnknownCommand(cmd)
542
545
543 def enabled(shortname=True):
546 def enabled(shortname=True):
544 '''return a dict of {name: desc} of extensions'''
547 '''return a dict of {name: desc} of extensions'''
545 exts = {}
548 exts = {}
546 for ename, ext in extensions():
549 for ename, ext in extensions():
547 doc = (gettext(ext.__doc__) or _('(no help text available)'))
550 doc = (gettext(ext.__doc__) or _('(no help text available)'))
548 if shortname:
551 if shortname:
549 ename = ename.split('.')[-1]
552 ename = ename.split('.')[-1]
550 exts[ename] = doc.splitlines()[0].strip()
553 exts[ename] = doc.splitlines()[0].strip()
551
554
552 return exts
555 return exts
553
556
554 def notloaded():
557 def notloaded():
555 '''return short names of extensions that failed to load'''
558 '''return short names of extensions that failed to load'''
556 return [name for name, mod in _extensions.iteritems() if mod is None]
559 return [name for name, mod in _extensions.iteritems() if mod is None]
557
560
558 def moduleversion(module):
561 def moduleversion(module):
559 '''return version information from given module as a string'''
562 '''return version information from given module as a string'''
560 if (util.safehasattr(module, 'getversion')
563 if (util.safehasattr(module, 'getversion')
561 and callable(module.getversion)):
564 and callable(module.getversion)):
562 version = module.getversion()
565 version = module.getversion()
563 elif util.safehasattr(module, '__version__'):
566 elif util.safehasattr(module, '__version__'):
564 version = module.__version__
567 version = module.__version__
565 else:
568 else:
566 version = ''
569 version = ''
567 if isinstance(version, (list, tuple)):
570 if isinstance(version, (list, tuple)):
568 version = '.'.join(str(o) for o in version)
571 version = '.'.join(str(o) for o in version)
569 return version
572 return version
570
573
571 def ismoduleinternal(module):
574 def ismoduleinternal(module):
572 exttestedwith = getattr(module, 'testedwith', None)
575 exttestedwith = getattr(module, 'testedwith', None)
573 return exttestedwith == "ships-with-hg-core"
576 return exttestedwith == "ships-with-hg-core"
@@ -1,1593 +1,1608 b''
1 Test basic extension support
1 Test basic extension support
2
2
3 $ cat > foobar.py <<EOF
3 $ cat > foobar.py <<EOF
4 > import os
4 > import os
5 > from mercurial import commands, registrar
5 > from mercurial import commands, registrar
6 > cmdtable = {}
6 > cmdtable = {}
7 > command = registrar.command(cmdtable)
7 > command = registrar.command(cmdtable)
8 > def uisetup(ui):
8 > def uisetup(ui):
9 > ui.write("uisetup called\\n")
9 > ui.write("uisetup called\\n")
10 > ui.flush()
10 > ui.flush()
11 > def reposetup(ui, repo):
11 > def reposetup(ui, repo):
12 > ui.write("reposetup called for %s\\n" % os.path.basename(repo.root))
12 > ui.write("reposetup called for %s\\n" % os.path.basename(repo.root))
13 > ui.write("ui %s= repo.ui\\n" % (ui == repo.ui and "=" or "!"))
13 > ui.write("ui %s= repo.ui\\n" % (ui == repo.ui and "=" or "!"))
14 > ui.flush()
14 > ui.flush()
15 > @command('foo', [], 'hg foo')
15 > @command('foo', [], 'hg foo')
16 > def foo(ui, *args, **kwargs):
16 > def foo(ui, *args, **kwargs):
17 > ui.write("Foo\\n")
17 > ui.write("Foo\\n")
18 > @command('bar', [], 'hg bar', norepo=True)
18 > @command('bar', [], 'hg bar', norepo=True)
19 > def bar(ui, *args, **kwargs):
19 > def bar(ui, *args, **kwargs):
20 > ui.write("Bar\\n")
20 > ui.write("Bar\\n")
21 > EOF
21 > EOF
22 $ abspath=`pwd`/foobar.py
22 $ abspath=`pwd`/foobar.py
23
23
24 $ mkdir barfoo
24 $ mkdir barfoo
25 $ cp foobar.py barfoo/__init__.py
25 $ cp foobar.py barfoo/__init__.py
26 $ barfoopath=`pwd`/barfoo
26 $ barfoopath=`pwd`/barfoo
27
27
28 $ hg init a
28 $ hg init a
29 $ cd a
29 $ cd a
30 $ echo foo > file
30 $ echo foo > file
31 $ hg add file
31 $ hg add file
32 $ hg commit -m 'add file'
32 $ hg commit -m 'add file'
33
33
34 $ echo '[extensions]' >> $HGRCPATH
34 $ echo '[extensions]' >> $HGRCPATH
35 $ echo "foobar = $abspath" >> $HGRCPATH
35 $ echo "foobar = $abspath" >> $HGRCPATH
36 $ hg foo
36 $ hg foo
37 uisetup called
37 uisetup called
38 reposetup called for a
38 reposetup called for a
39 ui == repo.ui
39 ui == repo.ui
40 Foo
40 Foo
41
41
42 $ cd ..
42 $ cd ..
43 $ hg clone a b
43 $ hg clone a b
44 uisetup called
44 uisetup called
45 reposetup called for a
45 reposetup called for a
46 ui == repo.ui
46 ui == repo.ui
47 reposetup called for b
47 reposetup called for b
48 ui == repo.ui
48 ui == repo.ui
49 updating to branch default
49 updating to branch default
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51
51
52 $ hg bar
52 $ hg bar
53 uisetup called
53 uisetup called
54 Bar
54 Bar
55 $ echo 'foobar = !' >> $HGRCPATH
55 $ echo 'foobar = !' >> $HGRCPATH
56
56
57 module/__init__.py-style
57 module/__init__.py-style
58
58
59 $ echo "barfoo = $barfoopath" >> $HGRCPATH
59 $ echo "barfoo = $barfoopath" >> $HGRCPATH
60 $ cd a
60 $ cd a
61 $ hg foo
61 $ hg foo
62 uisetup called
62 uisetup called
63 reposetup called for a
63 reposetup called for a
64 ui == repo.ui
64 ui == repo.ui
65 Foo
65 Foo
66 $ echo 'barfoo = !' >> $HGRCPATH
66 $ echo 'barfoo = !' >> $HGRCPATH
67
67
68 Check that extensions are loaded in phases:
68 Check that extensions are loaded in phases:
69
69
70 $ cat > foo.py <<EOF
70 $ cat > foo.py <<EOF
71 > import os
71 > import os
72 > name = os.path.basename(__file__).rsplit('.', 1)[0]
72 > name = os.path.basename(__file__).rsplit('.', 1)[0]
73 > print "1) %s imported" % name
73 > print "1) %s imported" % name
74 > def uisetup(ui):
74 > def uisetup(ui):
75 > print "2) %s uisetup" % name
75 > print "2) %s uisetup" % name
76 > def extsetup():
76 > def extsetup():
77 > print "3) %s extsetup" % name
77 > print "3) %s extsetup" % name
78 > def reposetup(ui, repo):
78 > def reposetup(ui, repo):
79 > print "4) %s reposetup" % name
79 > print "4) %s reposetup" % name
80 > EOF
80 > EOF
81
81
82 $ cp foo.py bar.py
82 $ cp foo.py bar.py
83 $ echo 'foo = foo.py' >> $HGRCPATH
83 $ echo 'foo = foo.py' >> $HGRCPATH
84 $ echo 'bar = bar.py' >> $HGRCPATH
84 $ echo 'bar = bar.py' >> $HGRCPATH
85
85
86 Command with no output, we just want to see the extensions loaded:
86 Command with no output, we just want to see the extensions loaded:
87
87
88 $ hg paths
88 $ hg paths
89 1) foo imported
89 1) foo imported
90 1) bar imported
90 1) bar imported
91 2) foo uisetup
91 2) foo uisetup
92 2) bar uisetup
92 2) bar uisetup
93 3) foo extsetup
93 3) foo extsetup
94 3) bar extsetup
94 3) bar extsetup
95 4) foo reposetup
95 4) foo reposetup
96 4) bar reposetup
96 4) bar reposetup
97
97
98 Check hgweb's load order:
98 Check hgweb's load order:
99
99
100 $ cat > hgweb.cgi <<EOF
100 $ cat > hgweb.cgi <<EOF
101 > #!/usr/bin/env python
101 > #!/usr/bin/env python
102 > from mercurial import demandimport; demandimport.enable()
102 > from mercurial import demandimport; demandimport.enable()
103 > from mercurial.hgweb import hgweb
103 > from mercurial.hgweb import hgweb
104 > from mercurial.hgweb import wsgicgi
104 > from mercurial.hgweb import wsgicgi
105 > application = hgweb('.', 'test repo')
105 > application = hgweb('.', 'test repo')
106 > wsgicgi.launch(application)
106 > wsgicgi.launch(application)
107 > EOF
107 > EOF
108
108
109 $ REQUEST_METHOD='GET' PATH_INFO='/' SCRIPT_NAME='' QUERY_STRING='' \
109 $ REQUEST_METHOD='GET' PATH_INFO='/' SCRIPT_NAME='' QUERY_STRING='' \
110 > SERVER_PORT='80' SERVER_NAME='localhost' python hgweb.cgi \
110 > SERVER_PORT='80' SERVER_NAME='localhost' python hgweb.cgi \
111 > | grep '^[0-9]) ' # ignores HTML output
111 > | grep '^[0-9]) ' # ignores HTML output
112 1) foo imported
112 1) foo imported
113 1) bar imported
113 1) bar imported
114 2) foo uisetup
114 2) foo uisetup
115 2) bar uisetup
115 2) bar uisetup
116 3) foo extsetup
116 3) foo extsetup
117 3) bar extsetup
117 3) bar extsetup
118 4) foo reposetup
118 4) foo reposetup
119 4) bar reposetup
119 4) bar reposetup
120
120
121 $ echo 'foo = !' >> $HGRCPATH
121 $ echo 'foo = !' >> $HGRCPATH
122 $ echo 'bar = !' >> $HGRCPATH
122 $ echo 'bar = !' >> $HGRCPATH
123
123
124 Check "from __future__ import absolute_import" support for external libraries
124 Check "from __future__ import absolute_import" support for external libraries
125
125
126 #if windows
126 #if windows
127 $ PATHSEP=";"
127 $ PATHSEP=";"
128 #else
128 #else
129 $ PATHSEP=":"
129 $ PATHSEP=":"
130 #endif
130 #endif
131 $ export PATHSEP
131 $ export PATHSEP
132
132
133 $ mkdir $TESTTMP/libroot
133 $ mkdir $TESTTMP/libroot
134 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
134 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
135 $ mkdir $TESTTMP/libroot/mod
135 $ mkdir $TESTTMP/libroot/mod
136 $ touch $TESTTMP/libroot/mod/__init__.py
136 $ touch $TESTTMP/libroot/mod/__init__.py
137 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
137 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
138
138
139 #if absimport
139 #if absimport
140 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<EOF
140 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<EOF
141 > from __future__ import absolute_import
141 > from __future__ import absolute_import
142 > import ambig # should load "libroot/ambig.py"
142 > import ambig # should load "libroot/ambig.py"
143 > s = ambig.s
143 > s = ambig.s
144 > EOF
144 > EOF
145 $ cat > loadabs.py <<EOF
145 $ cat > loadabs.py <<EOF
146 > import mod.ambigabs as ambigabs
146 > import mod.ambigabs as ambigabs
147 > def extsetup():
147 > def extsetup():
148 > print 'ambigabs.s=%s' % ambigabs.s
148 > print 'ambigabs.s=%s' % ambigabs.s
149 > EOF
149 > EOF
150 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
150 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
151 ambigabs.s=libroot/ambig.py
151 ambigabs.s=libroot/ambig.py
152 $TESTTMP/a (glob)
152 $TESTTMP/a (glob)
153 #endif
153 #endif
154
154
155 #if no-py3k
155 #if no-py3k
156 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<EOF
156 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<EOF
157 > import ambig # should load "libroot/mod/ambig.py"
157 > import ambig # should load "libroot/mod/ambig.py"
158 > s = ambig.s
158 > s = ambig.s
159 > EOF
159 > EOF
160 $ cat > loadrel.py <<EOF
160 $ cat > loadrel.py <<EOF
161 > import mod.ambigrel as ambigrel
161 > import mod.ambigrel as ambigrel
162 > def extsetup():
162 > def extsetup():
163 > print 'ambigrel.s=%s' % ambigrel.s
163 > print 'ambigrel.s=%s' % ambigrel.s
164 > EOF
164 > EOF
165 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
165 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
166 ambigrel.s=libroot/mod/ambig.py
166 ambigrel.s=libroot/mod/ambig.py
167 $TESTTMP/a (glob)
167 $TESTTMP/a (glob)
168 #endif
168 #endif
169
169
170 Check absolute/relative import of extension specific modules
170 Check absolute/relative import of extension specific modules
171
171
172 $ mkdir $TESTTMP/extroot
172 $ mkdir $TESTTMP/extroot
173 $ cat > $TESTTMP/extroot/bar.py <<EOF
173 $ cat > $TESTTMP/extroot/bar.py <<EOF
174 > s = 'this is extroot.bar'
174 > s = 'this is extroot.bar'
175 > EOF
175 > EOF
176 $ mkdir $TESTTMP/extroot/sub1
176 $ mkdir $TESTTMP/extroot/sub1
177 $ cat > $TESTTMP/extroot/sub1/__init__.py <<EOF
177 $ cat > $TESTTMP/extroot/sub1/__init__.py <<EOF
178 > s = 'this is extroot.sub1.__init__'
178 > s = 'this is extroot.sub1.__init__'
179 > EOF
179 > EOF
180 $ cat > $TESTTMP/extroot/sub1/baz.py <<EOF
180 $ cat > $TESTTMP/extroot/sub1/baz.py <<EOF
181 > s = 'this is extroot.sub1.baz'
181 > s = 'this is extroot.sub1.baz'
182 > EOF
182 > EOF
183 $ cat > $TESTTMP/extroot/__init__.py <<EOF
183 $ cat > $TESTTMP/extroot/__init__.py <<EOF
184 > s = 'this is extroot.__init__'
184 > s = 'this is extroot.__init__'
185 > import foo
185 > import foo
186 > def extsetup(ui):
186 > def extsetup(ui):
187 > ui.write('(extroot) ', foo.func(), '\n')
187 > ui.write('(extroot) ', foo.func(), '\n')
188 > ui.flush()
188 > ui.flush()
189 > EOF
189 > EOF
190
190
191 $ cat > $TESTTMP/extroot/foo.py <<EOF
191 $ cat > $TESTTMP/extroot/foo.py <<EOF
192 > # test absolute import
192 > # test absolute import
193 > buf = []
193 > buf = []
194 > def func():
194 > def func():
195 > # "not locals" case
195 > # "not locals" case
196 > import extroot.bar
196 > import extroot.bar
197 > buf.append('import extroot.bar in func(): %s' % extroot.bar.s)
197 > buf.append('import extroot.bar in func(): %s' % extroot.bar.s)
198 > return '\n(extroot) '.join(buf)
198 > return '\n(extroot) '.join(buf)
199 > # "fromlist == ('*',)" case
199 > # "fromlist == ('*',)" case
200 > from extroot.bar import *
200 > from extroot.bar import *
201 > buf.append('from extroot.bar import *: %s' % s)
201 > buf.append('from extroot.bar import *: %s' % s)
202 > # "not fromlist" and "if '.' in name" case
202 > # "not fromlist" and "if '.' in name" case
203 > import extroot.sub1.baz
203 > import extroot.sub1.baz
204 > buf.append('import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
204 > buf.append('import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
205 > # "not fromlist" and NOT "if '.' in name" case
205 > # "not fromlist" and NOT "if '.' in name" case
206 > import extroot
206 > import extroot
207 > buf.append('import extroot: %s' % extroot.s)
207 > buf.append('import extroot: %s' % extroot.s)
208 > # NOT "not fromlist" and NOT "level != -1" case
208 > # NOT "not fromlist" and NOT "level != -1" case
209 > from extroot.bar import s
209 > from extroot.bar import s
210 > buf.append('from extroot.bar import s: %s' % s)
210 > buf.append('from extroot.bar import s: %s' % s)
211 > EOF
211 > EOF
212 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
212 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
213 (extroot) from extroot.bar import *: this is extroot.bar
213 (extroot) from extroot.bar import *: this is extroot.bar
214 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
214 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
215 (extroot) import extroot: this is extroot.__init__
215 (extroot) import extroot: this is extroot.__init__
216 (extroot) from extroot.bar import s: this is extroot.bar
216 (extroot) from extroot.bar import s: this is extroot.bar
217 (extroot) import extroot.bar in func(): this is extroot.bar
217 (extroot) import extroot.bar in func(): this is extroot.bar
218 $TESTTMP/a (glob)
218 $TESTTMP/a (glob)
219
219
220 #if no-py3k
220 #if no-py3k
221 $ rm "$TESTTMP"/extroot/foo.*
221 $ rm "$TESTTMP"/extroot/foo.*
222 $ cat > $TESTTMP/extroot/foo.py <<EOF
222 $ cat > $TESTTMP/extroot/foo.py <<EOF
223 > # test relative import
223 > # test relative import
224 > buf = []
224 > buf = []
225 > def func():
225 > def func():
226 > # "not locals" case
226 > # "not locals" case
227 > import bar
227 > import bar
228 > buf.append('import bar in func(): %s' % bar.s)
228 > buf.append('import bar in func(): %s' % bar.s)
229 > return '\n(extroot) '.join(buf)
229 > return '\n(extroot) '.join(buf)
230 > # "fromlist == ('*',)" case
230 > # "fromlist == ('*',)" case
231 > from bar import *
231 > from bar import *
232 > buf.append('from bar import *: %s' % s)
232 > buf.append('from bar import *: %s' % s)
233 > # "not fromlist" and "if '.' in name" case
233 > # "not fromlist" and "if '.' in name" case
234 > import sub1.baz
234 > import sub1.baz
235 > buf.append('import sub1.baz: %s' % sub1.baz.s)
235 > buf.append('import sub1.baz: %s' % sub1.baz.s)
236 > # "not fromlist" and NOT "if '.' in name" case
236 > # "not fromlist" and NOT "if '.' in name" case
237 > import sub1
237 > import sub1
238 > buf.append('import sub1: %s' % sub1.s)
238 > buf.append('import sub1: %s' % sub1.s)
239 > # NOT "not fromlist" and NOT "level != -1" case
239 > # NOT "not fromlist" and NOT "level != -1" case
240 > from bar import s
240 > from bar import s
241 > buf.append('from bar import s: %s' % s)
241 > buf.append('from bar import s: %s' % s)
242 > EOF
242 > EOF
243 $ hg --config extensions.extroot=$TESTTMP/extroot root
243 $ hg --config extensions.extroot=$TESTTMP/extroot root
244 (extroot) from bar import *: this is extroot.bar
244 (extroot) from bar import *: this is extroot.bar
245 (extroot) import sub1.baz: this is extroot.sub1.baz
245 (extroot) import sub1.baz: this is extroot.sub1.baz
246 (extroot) import sub1: this is extroot.sub1.__init__
246 (extroot) import sub1: this is extroot.sub1.__init__
247 (extroot) from bar import s: this is extroot.bar
247 (extroot) from bar import s: this is extroot.bar
248 (extroot) import bar in func(): this is extroot.bar
248 (extroot) import bar in func(): this is extroot.bar
249 $TESTTMP/a (glob)
249 $TESTTMP/a (glob)
250 #endif
250 #endif
251
251
252 #if demandimport absimport
252 #if demandimport absimport
253
253
254 Examine whether module loading is delayed until actual referring, even
254 Examine whether module loading is delayed until actual referring, even
255 though module is imported with "absolute_import" feature.
255 though module is imported with "absolute_import" feature.
256
256
257 Files below in each packages are used for described purpose:
257 Files below in each packages are used for described purpose:
258
258
259 - "called": examine whether "from MODULE import ATTR" works correctly
259 - "called": examine whether "from MODULE import ATTR" works correctly
260 - "unused": examine whether loading is delayed correctly
260 - "unused": examine whether loading is delayed correctly
261 - "used": examine whether "from PACKAGE import MODULE" works correctly
261 - "used": examine whether "from PACKAGE import MODULE" works correctly
262
262
263 Package hierarchy is needed to examine whether demand importing works
263 Package hierarchy is needed to examine whether demand importing works
264 as expected for "from SUB.PACK.AGE import MODULE".
264 as expected for "from SUB.PACK.AGE import MODULE".
265
265
266 Setup "external library" to be imported with "absolute_import"
266 Setup "external library" to be imported with "absolute_import"
267 feature.
267 feature.
268
268
269 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
269 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
270 $ touch $TESTTMP/extlibroot/__init__.py
270 $ touch $TESTTMP/extlibroot/__init__.py
271 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
271 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
272 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
272 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
273
273
274 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<EOF
274 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<EOF
275 > def func():
275 > def func():
276 > return "this is extlibroot.lsub1.lsub2.called.func()"
276 > return "this is extlibroot.lsub1.lsub2.called.func()"
277 > EOF
277 > EOF
278 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<EOF
278 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<EOF
279 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
279 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
280 > EOF
280 > EOF
281 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<EOF
281 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<EOF
282 > detail = "this is extlibroot.lsub1.lsub2.used"
282 > detail = "this is extlibroot.lsub1.lsub2.used"
283 > EOF
283 > EOF
284
284
285 Setup sub-package of "external library", which causes instantiation of
285 Setup sub-package of "external library", which causes instantiation of
286 demandmod in "recurse down the module chain" code path. Relative
286 demandmod in "recurse down the module chain" code path. Relative
287 importing with "absolute_import" feature isn't tested, because "level
287 importing with "absolute_import" feature isn't tested, because "level
288 >=1 " doesn't cause instantiation of demandmod.
288 >=1 " doesn't cause instantiation of demandmod.
289
289
290 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
290 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
291 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<EOF
291 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<EOF
292 > detail = "this is extlibroot.recursedown.abs.used"
292 > detail = "this is extlibroot.recursedown.abs.used"
293 > EOF
293 > EOF
294 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<EOF
294 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<EOF
295 > from __future__ import absolute_import
295 > from __future__ import absolute_import
296 > from extlibroot.recursedown.abs.used import detail
296 > from extlibroot.recursedown.abs.used import detail
297 > EOF
297 > EOF
298
298
299 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
299 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
300 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<EOF
300 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<EOF
301 > detail = "this is extlibroot.recursedown.legacy.used"
301 > detail = "this is extlibroot.recursedown.legacy.used"
302 > EOF
302 > EOF
303 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<EOF
303 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<EOF
304 > # legacy style (level == -1) import
304 > # legacy style (level == -1) import
305 > from extlibroot.recursedown.legacy.used import detail
305 > from extlibroot.recursedown.legacy.used import detail
306 > EOF
306 > EOF
307
307
308 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<EOF
308 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<EOF
309 > from __future__ import absolute_import
309 > from __future__ import absolute_import
310 > from extlibroot.recursedown.abs import detail as absdetail
310 > from extlibroot.recursedown.abs import detail as absdetail
311 > from .legacy import detail as legacydetail
311 > from .legacy import detail as legacydetail
312 > EOF
312 > EOF
313
313
314 Setup extension local modules to be imported with "absolute_import"
314 Setup extension local modules to be imported with "absolute_import"
315 feature.
315 feature.
316
316
317 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
317 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
318 $ touch $TESTTMP/absextroot/xsub1/__init__.py
318 $ touch $TESTTMP/absextroot/xsub1/__init__.py
319 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
319 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
320
320
321 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<EOF
321 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<EOF
322 > def func():
322 > def func():
323 > return "this is absextroot.xsub1.xsub2.called.func()"
323 > return "this is absextroot.xsub1.xsub2.called.func()"
324 > EOF
324 > EOF
325 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<EOF
325 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<EOF
326 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
326 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
327 > EOF
327 > EOF
328 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<EOF
328 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<EOF
329 > detail = "this is absextroot.xsub1.xsub2.used"
329 > detail = "this is absextroot.xsub1.xsub2.used"
330 > EOF
330 > EOF
331
331
332 Setup extension local modules to examine whether demand importing
332 Setup extension local modules to examine whether demand importing
333 works as expected in "level > 1" case.
333 works as expected in "level > 1" case.
334
334
335 $ cat > $TESTTMP/absextroot/relimportee.py <<EOF
335 $ cat > $TESTTMP/absextroot/relimportee.py <<EOF
336 > detail = "this is absextroot.relimportee"
336 > detail = "this is absextroot.relimportee"
337 > EOF
337 > EOF
338 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<EOF
338 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<EOF
339 > from __future__ import absolute_import
339 > from __future__ import absolute_import
340 > from ... import relimportee
340 > from ... import relimportee
341 > detail = "this relimporter imports %r" % (relimportee.detail)
341 > detail = "this relimporter imports %r" % (relimportee.detail)
342 > EOF
342 > EOF
343
343
344 Setup modules, which actually import extension local modules at
344 Setup modules, which actually import extension local modules at
345 runtime.
345 runtime.
346
346
347 $ cat > $TESTTMP/absextroot/absolute.py << EOF
347 $ cat > $TESTTMP/absextroot/absolute.py << EOF
348 > from __future__ import absolute_import
348 > from __future__ import absolute_import
349 >
349 >
350 > # import extension local modules absolutely (level = 0)
350 > # import extension local modules absolutely (level = 0)
351 > from absextroot.xsub1.xsub2 import used, unused
351 > from absextroot.xsub1.xsub2 import used, unused
352 > from absextroot.xsub1.xsub2.called import func
352 > from absextroot.xsub1.xsub2.called import func
353 >
353 >
354 > def getresult():
354 > def getresult():
355 > result = []
355 > result = []
356 > result.append(used.detail)
356 > result.append(used.detail)
357 > result.append(func())
357 > result.append(func())
358 > return result
358 > return result
359 > EOF
359 > EOF
360
360
361 $ cat > $TESTTMP/absextroot/relative.py << EOF
361 $ cat > $TESTTMP/absextroot/relative.py << EOF
362 > from __future__ import absolute_import
362 > from __future__ import absolute_import
363 >
363 >
364 > # import extension local modules relatively (level == 1)
364 > # import extension local modules relatively (level == 1)
365 > from .xsub1.xsub2 import used, unused
365 > from .xsub1.xsub2 import used, unused
366 > from .xsub1.xsub2.called import func
366 > from .xsub1.xsub2.called import func
367 >
367 >
368 > # import a module, which implies "importing with level > 1"
368 > # import a module, which implies "importing with level > 1"
369 > from .xsub1.xsub2 import relimporter
369 > from .xsub1.xsub2 import relimporter
370 >
370 >
371 > def getresult():
371 > def getresult():
372 > result = []
372 > result = []
373 > result.append(used.detail)
373 > result.append(used.detail)
374 > result.append(func())
374 > result.append(func())
375 > result.append(relimporter.detail)
375 > result.append(relimporter.detail)
376 > return result
376 > return result
377 > EOF
377 > EOF
378
378
379 Setup main procedure of extension.
379 Setup main procedure of extension.
380
380
381 $ cat > $TESTTMP/absextroot/__init__.py <<EOF
381 $ cat > $TESTTMP/absextroot/__init__.py <<EOF
382 > from __future__ import absolute_import
382 > from __future__ import absolute_import
383 > from mercurial import registrar
383 > from mercurial import registrar
384 > cmdtable = {}
384 > cmdtable = {}
385 > command = registrar.command(cmdtable)
385 > command = registrar.command(cmdtable)
386 >
386 >
387 > # "absolute" and "relative" shouldn't be imported before actual
387 > # "absolute" and "relative" shouldn't be imported before actual
388 > # command execution, because (1) they import same modules, and (2)
388 > # command execution, because (1) they import same modules, and (2)
389 > # preceding import (= instantiate "demandmod" object instead of
389 > # preceding import (= instantiate "demandmod" object instead of
390 > # real "module" object) might hide problem of succeeding import.
390 > # real "module" object) might hide problem of succeeding import.
391 >
391 >
392 > @command('showabsolute', [], norepo=True)
392 > @command('showabsolute', [], norepo=True)
393 > def showabsolute(ui, *args, **opts):
393 > def showabsolute(ui, *args, **opts):
394 > from absextroot import absolute
394 > from absextroot import absolute
395 > ui.write('ABS: %s\n' % '\nABS: '.join(absolute.getresult()))
395 > ui.write('ABS: %s\n' % '\nABS: '.join(absolute.getresult()))
396 >
396 >
397 > @command('showrelative', [], norepo=True)
397 > @command('showrelative', [], norepo=True)
398 > def showrelative(ui, *args, **opts):
398 > def showrelative(ui, *args, **opts):
399 > from . import relative
399 > from . import relative
400 > ui.write('REL: %s\n' % '\nREL: '.join(relative.getresult()))
400 > ui.write('REL: %s\n' % '\nREL: '.join(relative.getresult()))
401 >
401 >
402 > # import modules from external library
402 > # import modules from external library
403 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
403 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
404 > from extlibroot.lsub1.lsub2.called import func as lfunc
404 > from extlibroot.lsub1.lsub2.called import func as lfunc
405 > from extlibroot.recursedown import absdetail, legacydetail
405 > from extlibroot.recursedown import absdetail, legacydetail
406 >
406 >
407 > def uisetup(ui):
407 > def uisetup(ui):
408 > result = []
408 > result = []
409 > result.append(lused.detail)
409 > result.append(lused.detail)
410 > result.append(lfunc())
410 > result.append(lfunc())
411 > result.append(absdetail)
411 > result.append(absdetail)
412 > result.append(legacydetail)
412 > result.append(legacydetail)
413 > ui.write('LIB: %s\n' % '\nLIB: '.join(result))
413 > ui.write('LIB: %s\n' % '\nLIB: '.join(result))
414 > EOF
414 > EOF
415
415
416 Examine module importing.
416 Examine module importing.
417
417
418 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
418 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
419 LIB: this is extlibroot.lsub1.lsub2.used
419 LIB: this is extlibroot.lsub1.lsub2.used
420 LIB: this is extlibroot.lsub1.lsub2.called.func()
420 LIB: this is extlibroot.lsub1.lsub2.called.func()
421 LIB: this is extlibroot.recursedown.abs.used
421 LIB: this is extlibroot.recursedown.abs.used
422 LIB: this is extlibroot.recursedown.legacy.used
422 LIB: this is extlibroot.recursedown.legacy.used
423 ABS: this is absextroot.xsub1.xsub2.used
423 ABS: this is absextroot.xsub1.xsub2.used
424 ABS: this is absextroot.xsub1.xsub2.called.func()
424 ABS: this is absextroot.xsub1.xsub2.called.func()
425
425
426 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
426 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
427 LIB: this is extlibroot.lsub1.lsub2.used
427 LIB: this is extlibroot.lsub1.lsub2.used
428 LIB: this is extlibroot.lsub1.lsub2.called.func()
428 LIB: this is extlibroot.lsub1.lsub2.called.func()
429 LIB: this is extlibroot.recursedown.abs.used
429 LIB: this is extlibroot.recursedown.abs.used
430 LIB: this is extlibroot.recursedown.legacy.used
430 LIB: this is extlibroot.recursedown.legacy.used
431 REL: this is absextroot.xsub1.xsub2.used
431 REL: this is absextroot.xsub1.xsub2.used
432 REL: this is absextroot.xsub1.xsub2.called.func()
432 REL: this is absextroot.xsub1.xsub2.called.func()
433 REL: this relimporter imports 'this is absextroot.relimportee'
433 REL: this relimporter imports 'this is absextroot.relimportee'
434
434
435 Examine whether sub-module is imported relatively as expected.
435 Examine whether sub-module is imported relatively as expected.
436
436
437 See also issue5208 for detail about example case on Python 3.x.
437 See also issue5208 for detail about example case on Python 3.x.
438
438
439 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
439 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
440 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
440 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
441
441
442 $ cat > $TESTTMP/notexist.py <<EOF
442 $ cat > $TESTTMP/notexist.py <<EOF
443 > text = 'notexist.py at root is loaded unintentionally\n'
443 > text = 'notexist.py at root is loaded unintentionally\n'
444 > EOF
444 > EOF
445
445
446 $ cat > $TESTTMP/checkrelativity.py <<EOF
446 $ cat > $TESTTMP/checkrelativity.py <<EOF
447 > from mercurial import registrar
447 > from mercurial import registrar
448 > cmdtable = {}
448 > cmdtable = {}
449 > command = registrar.command(cmdtable)
449 > command = registrar.command(cmdtable)
450 >
450 >
451 > # demand import avoids failure of importing notexist here
451 > # demand import avoids failure of importing notexist here
452 > import extlibroot.lsub1.lsub2.notexist
452 > import extlibroot.lsub1.lsub2.notexist
453 >
453 >
454 > @command('checkrelativity', [], norepo=True)
454 > @command('checkrelativity', [], norepo=True)
455 > def checkrelativity(ui, *args, **opts):
455 > def checkrelativity(ui, *args, **opts):
456 > try:
456 > try:
457 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
457 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
458 > return 1 # unintentional success
458 > return 1 # unintentional success
459 > except ImportError:
459 > except ImportError:
460 > pass # intentional failure
460 > pass # intentional failure
461 > EOF
461 > EOF
462
462
463 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
463 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
464
464
465 #endif
465 #endif
466
466
467 $ cd ..
467 $ cd ..
468
468
469 hide outer repo
469 hide outer repo
470 $ hg init
470 $ hg init
471
471
472 $ cat > empty.py <<EOF
472 $ cat > empty.py <<EOF
473 > '''empty cmdtable
473 > '''empty cmdtable
474 > '''
474 > '''
475 > cmdtable = {}
475 > cmdtable = {}
476 > EOF
476 > EOF
477 $ emptypath=`pwd`/empty.py
477 $ emptypath=`pwd`/empty.py
478 $ echo "empty = $emptypath" >> $HGRCPATH
478 $ echo "empty = $emptypath" >> $HGRCPATH
479 $ hg help empty
479 $ hg help empty
480 empty extension - empty cmdtable
480 empty extension - empty cmdtable
481
481
482 no commands defined
482 no commands defined
483
483
484
484
485 $ echo 'empty = !' >> $HGRCPATH
485 $ echo 'empty = !' >> $HGRCPATH
486
486
487 $ cat > debugextension.py <<EOF
487 $ cat > debugextension.py <<EOF
488 > '''only debugcommands
488 > '''only debugcommands
489 > '''
489 > '''
490 > from mercurial import registrar
490 > from mercurial import registrar
491 > cmdtable = {}
491 > cmdtable = {}
492 > command = registrar.command(cmdtable)
492 > command = registrar.command(cmdtable)
493 > @command('debugfoobar', [], 'hg debugfoobar')
493 > @command('debugfoobar', [], 'hg debugfoobar')
494 > def debugfoobar(ui, repo, *args, **opts):
494 > def debugfoobar(ui, repo, *args, **opts):
495 > "yet another debug command"
495 > "yet another debug command"
496 > pass
496 > pass
497 > @command('foo', [], 'hg foo')
497 > @command('foo', [], 'hg foo')
498 > def foo(ui, repo, *args, **opts):
498 > def foo(ui, repo, *args, **opts):
499 > """yet another foo command
499 > """yet another foo command
500 > This command has been DEPRECATED since forever.
500 > This command has been DEPRECATED since forever.
501 > """
501 > """
502 > pass
502 > pass
503 > EOF
503 > EOF
504 $ debugpath=`pwd`/debugextension.py
504 $ debugpath=`pwd`/debugextension.py
505 $ echo "debugextension = $debugpath" >> $HGRCPATH
505 $ echo "debugextension = $debugpath" >> $HGRCPATH
506
506
507 $ hg help debugextension
507 $ hg help debugextension
508 hg debugextensions
508 hg debugextensions
509
509
510 show information about active extensions
510 show information about active extensions
511
511
512 options:
512 options:
513
513
514 (some details hidden, use --verbose to show complete help)
514 (some details hidden, use --verbose to show complete help)
515
515
516
516
517 $ hg --verbose help debugextension
517 $ hg --verbose help debugextension
518 hg debugextensions
518 hg debugextensions
519
519
520 show information about active extensions
520 show information about active extensions
521
521
522 options:
522 options:
523
523
524 -T --template TEMPLATE display with template (EXPERIMENTAL)
524 -T --template TEMPLATE display with template (EXPERIMENTAL)
525
525
526 global options ([+] can be repeated):
526 global options ([+] can be repeated):
527
527
528 -R --repository REPO repository root directory or name of overlay bundle
528 -R --repository REPO repository root directory or name of overlay bundle
529 file
529 file
530 --cwd DIR change working directory
530 --cwd DIR change working directory
531 -y --noninteractive do not prompt, automatically pick the first choice for
531 -y --noninteractive do not prompt, automatically pick the first choice for
532 all prompts
532 all prompts
533 -q --quiet suppress output
533 -q --quiet suppress output
534 -v --verbose enable additional output
534 -v --verbose enable additional output
535 --color TYPE when to colorize (boolean, always, auto, never, or
535 --color TYPE when to colorize (boolean, always, auto, never, or
536 debug)
536 debug)
537 --config CONFIG [+] set/override config option (use 'section.name=value')
537 --config CONFIG [+] set/override config option (use 'section.name=value')
538 --debug enable debugging output
538 --debug enable debugging output
539 --debugger start debugger
539 --debugger start debugger
540 --encoding ENCODE set the charset encoding (default: ascii)
540 --encoding ENCODE set the charset encoding (default: ascii)
541 --encodingmode MODE set the charset encoding mode (default: strict)
541 --encodingmode MODE set the charset encoding mode (default: strict)
542 --traceback always print a traceback on exception
542 --traceback always print a traceback on exception
543 --time time how long the command takes
543 --time time how long the command takes
544 --profile print command execution profile
544 --profile print command execution profile
545 --version output version information and exit
545 --version output version information and exit
546 -h --help display help and exit
546 -h --help display help and exit
547 --hidden consider hidden changesets
547 --hidden consider hidden changesets
548 --pager TYPE when to paginate (boolean, always, auto, or never)
548 --pager TYPE when to paginate (boolean, always, auto, or never)
549 (default: auto)
549 (default: auto)
550
550
551
551
552
552
553
553
554
554
555
555
556 $ hg --debug help debugextension
556 $ hg --debug help debugextension
557 hg debugextensions
557 hg debugextensions
558
558
559 show information about active extensions
559 show information about active extensions
560
560
561 options:
561 options:
562
562
563 -T --template TEMPLATE display with template (EXPERIMENTAL)
563 -T --template TEMPLATE display with template (EXPERIMENTAL)
564
564
565 global options ([+] can be repeated):
565 global options ([+] can be repeated):
566
566
567 -R --repository REPO repository root directory or name of overlay bundle
567 -R --repository REPO repository root directory or name of overlay bundle
568 file
568 file
569 --cwd DIR change working directory
569 --cwd DIR change working directory
570 -y --noninteractive do not prompt, automatically pick the first choice for
570 -y --noninteractive do not prompt, automatically pick the first choice for
571 all prompts
571 all prompts
572 -q --quiet suppress output
572 -q --quiet suppress output
573 -v --verbose enable additional output
573 -v --verbose enable additional output
574 --color TYPE when to colorize (boolean, always, auto, never, or
574 --color TYPE when to colorize (boolean, always, auto, never, or
575 debug)
575 debug)
576 --config CONFIG [+] set/override config option (use 'section.name=value')
576 --config CONFIG [+] set/override config option (use 'section.name=value')
577 --debug enable debugging output
577 --debug enable debugging output
578 --debugger start debugger
578 --debugger start debugger
579 --encoding ENCODE set the charset encoding (default: ascii)
579 --encoding ENCODE set the charset encoding (default: ascii)
580 --encodingmode MODE set the charset encoding mode (default: strict)
580 --encodingmode MODE set the charset encoding mode (default: strict)
581 --traceback always print a traceback on exception
581 --traceback always print a traceback on exception
582 --time time how long the command takes
582 --time time how long the command takes
583 --profile print command execution profile
583 --profile print command execution profile
584 --version output version information and exit
584 --version output version information and exit
585 -h --help display help and exit
585 -h --help display help and exit
586 --hidden consider hidden changesets
586 --hidden consider hidden changesets
587 --pager TYPE when to paginate (boolean, always, auto, or never)
587 --pager TYPE when to paginate (boolean, always, auto, or never)
588 (default: auto)
588 (default: auto)
589
589
590
590
591
591
592
592
593
593
594 $ echo 'debugextension = !' >> $HGRCPATH
594 $ echo 'debugextension = !' >> $HGRCPATH
595
595
596 Asking for help about a deprecated extension should do something useful:
596 Asking for help about a deprecated extension should do something useful:
597
597
598 $ hg help glog
598 $ hg help glog
599 'glog' is provided by the following extension:
599 'glog' is provided by the following extension:
600
600
601 graphlog command to view revision graphs from a shell (DEPRECATED)
601 graphlog command to view revision graphs from a shell (DEPRECATED)
602
602
603 (use 'hg help extensions' for information on enabling extensions)
603 (use 'hg help extensions' for information on enabling extensions)
604
604
605 Extension module help vs command help:
605 Extension module help vs command help:
606
606
607 $ echo 'extdiff =' >> $HGRCPATH
607 $ echo 'extdiff =' >> $HGRCPATH
608 $ hg help extdiff
608 $ hg help extdiff
609 hg extdiff [OPT]... [FILE]...
609 hg extdiff [OPT]... [FILE]...
610
610
611 use external program to diff repository (or selected files)
611 use external program to diff repository (or selected files)
612
612
613 Show differences between revisions for the specified files, using an
613 Show differences between revisions for the specified files, using an
614 external program. The default program used is diff, with default options
614 external program. The default program used is diff, with default options
615 "-Npru".
615 "-Npru".
616
616
617 To select a different program, use the -p/--program option. The program
617 To select a different program, use the -p/--program option. The program
618 will be passed the names of two directories to compare. To pass additional
618 will be passed the names of two directories to compare. To pass additional
619 options to the program, use -o/--option. These will be passed before the
619 options to the program, use -o/--option. These will be passed before the
620 names of the directories to compare.
620 names of the directories to compare.
621
621
622 When two revision arguments are given, then changes are shown between
622 When two revision arguments are given, then changes are shown between
623 those revisions. If only one revision is specified then that revision is
623 those revisions. If only one revision is specified then that revision is
624 compared to the working directory, and, when no revisions are specified,
624 compared to the working directory, and, when no revisions are specified,
625 the working directory files are compared to its parent.
625 the working directory files are compared to its parent.
626
626
627 (use 'hg help -e extdiff' to show help for the extdiff extension)
627 (use 'hg help -e extdiff' to show help for the extdiff extension)
628
628
629 options ([+] can be repeated):
629 options ([+] can be repeated):
630
630
631 -p --program CMD comparison program to run
631 -p --program CMD comparison program to run
632 -o --option OPT [+] pass option to comparison program
632 -o --option OPT [+] pass option to comparison program
633 -r --rev REV [+] revision
633 -r --rev REV [+] revision
634 -c --change REV change made by revision
634 -c --change REV change made by revision
635 --patch compare patches for two revisions
635 --patch compare patches for two revisions
636 -I --include PATTERN [+] include names matching the given patterns
636 -I --include PATTERN [+] include names matching the given patterns
637 -X --exclude PATTERN [+] exclude names matching the given patterns
637 -X --exclude PATTERN [+] exclude names matching the given patterns
638 -S --subrepos recurse into subrepositories
638 -S --subrepos recurse into subrepositories
639
639
640 (some details hidden, use --verbose to show complete help)
640 (some details hidden, use --verbose to show complete help)
641
641
642
642
643
643
644
644
645
645
646
646
647
647
648
648
649
649
650
650
651 $ hg help --extension extdiff
651 $ hg help --extension extdiff
652 extdiff extension - command to allow external programs to compare revisions
652 extdiff extension - command to allow external programs to compare revisions
653
653
654 The extdiff Mercurial extension allows you to use external programs to compare
654 The extdiff Mercurial extension allows you to use external programs to compare
655 revisions, or revision with working directory. The external diff programs are
655 revisions, or revision with working directory. The external diff programs are
656 called with a configurable set of options and two non-option arguments: paths
656 called with a configurable set of options and two non-option arguments: paths
657 to directories containing snapshots of files to compare.
657 to directories containing snapshots of files to compare.
658
658
659 The extdiff extension also allows you to configure new diff commands, so you
659 The extdiff extension also allows you to configure new diff commands, so you
660 do not need to type 'hg extdiff -p kdiff3' always.
660 do not need to type 'hg extdiff -p kdiff3' always.
661
661
662 [extdiff]
662 [extdiff]
663 # add new command that runs GNU diff(1) in 'context diff' mode
663 # add new command that runs GNU diff(1) in 'context diff' mode
664 cdiff = gdiff -Nprc5
664 cdiff = gdiff -Nprc5
665 ## or the old way:
665 ## or the old way:
666 #cmd.cdiff = gdiff
666 #cmd.cdiff = gdiff
667 #opts.cdiff = -Nprc5
667 #opts.cdiff = -Nprc5
668
668
669 # add new command called meld, runs meld (no need to name twice). If
669 # add new command called meld, runs meld (no need to name twice). If
670 # the meld executable is not available, the meld tool in [merge-tools]
670 # the meld executable is not available, the meld tool in [merge-tools]
671 # will be used, if available
671 # will be used, if available
672 meld =
672 meld =
673
673
674 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
674 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
675 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
675 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
676 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
676 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
677 # your .vimrc
677 # your .vimrc
678 vimdiff = gvim -f "+next" \
678 vimdiff = gvim -f "+next" \
679 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
679 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
680
680
681 Tool arguments can include variables that are expanded at runtime:
681 Tool arguments can include variables that are expanded at runtime:
682
682
683 $parent1, $plabel1 - filename, descriptive label of first parent
683 $parent1, $plabel1 - filename, descriptive label of first parent
684 $child, $clabel - filename, descriptive label of child revision
684 $child, $clabel - filename, descriptive label of child revision
685 $parent2, $plabel2 - filename, descriptive label of second parent
685 $parent2, $plabel2 - filename, descriptive label of second parent
686 $root - repository root
686 $root - repository root
687 $parent is an alias for $parent1.
687 $parent is an alias for $parent1.
688
688
689 The extdiff extension will look in your [diff-tools] and [merge-tools]
689 The extdiff extension will look in your [diff-tools] and [merge-tools]
690 sections for diff tool arguments, when none are specified in [extdiff].
690 sections for diff tool arguments, when none are specified in [extdiff].
691
691
692 [extdiff]
692 [extdiff]
693 kdiff3 =
693 kdiff3 =
694
694
695 [diff-tools]
695 [diff-tools]
696 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
696 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
697
697
698 You can use -I/-X and list of file or directory names like normal 'hg diff'
698 You can use -I/-X and list of file or directory names like normal 'hg diff'
699 command. The extdiff extension makes snapshots of only needed files, so
699 command. The extdiff extension makes snapshots of only needed files, so
700 running the external diff program will actually be pretty fast (at least
700 running the external diff program will actually be pretty fast (at least
701 faster than having to compare the entire tree).
701 faster than having to compare the entire tree).
702
702
703 list of commands:
703 list of commands:
704
704
705 extdiff use external program to diff repository (or selected files)
705 extdiff use external program to diff repository (or selected files)
706
706
707 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
707 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
708
708
709
709
710
710
711
711
712
712
713
713
714
714
715
715
716
716
717
717
718
718
719
719
720
720
721
721
722
722
723
723
724 $ echo 'extdiff = !' >> $HGRCPATH
724 $ echo 'extdiff = !' >> $HGRCPATH
725
725
726 Test help topic with same name as extension
726 Test help topic with same name as extension
727
727
728 $ cat > multirevs.py <<EOF
728 $ cat > multirevs.py <<EOF
729 > from mercurial import commands, registrar
729 > from mercurial import commands, registrar
730 > cmdtable = {}
730 > cmdtable = {}
731 > command = registrar.command(cmdtable)
731 > command = registrar.command(cmdtable)
732 > """multirevs extension
732 > """multirevs extension
733 > Big multi-line module docstring."""
733 > Big multi-line module docstring."""
734 > @command('multirevs', [], 'ARG', norepo=True)
734 > @command('multirevs', [], 'ARG', norepo=True)
735 > def multirevs(ui, repo, arg, *args, **opts):
735 > def multirevs(ui, repo, arg, *args, **opts):
736 > """multirevs command"""
736 > """multirevs command"""
737 > pass
737 > pass
738 > EOF
738 > EOF
739 $ echo "multirevs = multirevs.py" >> $HGRCPATH
739 $ echo "multirevs = multirevs.py" >> $HGRCPATH
740
740
741 $ hg help multirevs | tail
741 $ hg help multirevs | tail
742 bookmark (this works because the last revision of the revset is used):
742 bookmark (this works because the last revision of the revset is used):
743
743
744 hg update :@
744 hg update :@
745
745
746 - Show diff between tags 1.3 and 1.5 (this works because the first and the
746 - Show diff between tags 1.3 and 1.5 (this works because the first and the
747 last revisions of the revset are used):
747 last revisions of the revset are used):
748
748
749 hg diff -r 1.3::1.5
749 hg diff -r 1.3::1.5
750
750
751 use 'hg help -c multirevs' to see help for the multirevs command
751 use 'hg help -c multirevs' to see help for the multirevs command
752
752
753
753
754
754
755
755
756
756
757
757
758 $ hg help -c multirevs
758 $ hg help -c multirevs
759 hg multirevs ARG
759 hg multirevs ARG
760
760
761 multirevs command
761 multirevs command
762
762
763 (some details hidden, use --verbose to show complete help)
763 (some details hidden, use --verbose to show complete help)
764
764
765
765
766
766
767 $ hg multirevs
767 $ hg multirevs
768 hg multirevs: invalid arguments
768 hg multirevs: invalid arguments
769 hg multirevs ARG
769 hg multirevs ARG
770
770
771 multirevs command
771 multirevs command
772
772
773 (use 'hg multirevs -h' to show more help)
773 (use 'hg multirevs -h' to show more help)
774 [255]
774 [255]
775
775
776
776
777
777
778 $ echo "multirevs = !" >> $HGRCPATH
778 $ echo "multirevs = !" >> $HGRCPATH
779
779
780 Issue811: Problem loading extensions twice (by site and by user)
780 Issue811: Problem loading extensions twice (by site and by user)
781
781
782 $ cat <<EOF >> $HGRCPATH
782 $ cat <<EOF >> $HGRCPATH
783 > mq =
783 > mq =
784 > strip =
784 > strip =
785 > hgext.mq =
785 > hgext.mq =
786 > hgext/mq =
786 > hgext/mq =
787 > EOF
787 > EOF
788
788
789 Show extensions:
789 Show extensions:
790 (note that mq force load strip, also checking it's not loaded twice)
790 (note that mq force load strip, also checking it's not loaded twice)
791
791
792 $ hg debugextensions
792 $ hg debugextensions
793 mq
793 mq
794 strip
794 strip
795
795
796 For extensions, which name matches one of its commands, help
796 For extensions, which name matches one of its commands, help
797 message should ask '-v -e' to get list of built-in aliases
797 message should ask '-v -e' to get list of built-in aliases
798 along with extension help itself
798 along with extension help itself
799
799
800 $ mkdir $TESTTMP/d
800 $ mkdir $TESTTMP/d
801 $ cat > $TESTTMP/d/dodo.py <<EOF
801 $ cat > $TESTTMP/d/dodo.py <<EOF
802 > """
802 > """
803 > This is an awesome 'dodo' extension. It does nothing and
803 > This is an awesome 'dodo' extension. It does nothing and
804 > writes 'Foo foo'
804 > writes 'Foo foo'
805 > """
805 > """
806 > from mercurial import commands, registrar
806 > from mercurial import commands, registrar
807 > cmdtable = {}
807 > cmdtable = {}
808 > command = registrar.command(cmdtable)
808 > command = registrar.command(cmdtable)
809 > @command('dodo', [], 'hg dodo')
809 > @command('dodo', [], 'hg dodo')
810 > def dodo(ui, *args, **kwargs):
810 > def dodo(ui, *args, **kwargs):
811 > """Does nothing"""
811 > """Does nothing"""
812 > ui.write("I do nothing. Yay\\n")
812 > ui.write("I do nothing. Yay\\n")
813 > @command('foofoo', [], 'hg foofoo')
813 > @command('foofoo', [], 'hg foofoo')
814 > def foofoo(ui, *args, **kwargs):
814 > def foofoo(ui, *args, **kwargs):
815 > """Writes 'Foo foo'"""
815 > """Writes 'Foo foo'"""
816 > ui.write("Foo foo\\n")
816 > ui.write("Foo foo\\n")
817 > EOF
817 > EOF
818 $ dodopath=$TESTTMP/d/dodo.py
818 $ dodopath=$TESTTMP/d/dodo.py
819
819
820 $ echo "dodo = $dodopath" >> $HGRCPATH
820 $ echo "dodo = $dodopath" >> $HGRCPATH
821
821
822 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
822 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
823 $ hg help -e dodo
823 $ hg help -e dodo
824 dodo extension -
824 dodo extension -
825
825
826 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
826 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
827
827
828 list of commands:
828 list of commands:
829
829
830 dodo Does nothing
830 dodo Does nothing
831 foofoo Writes 'Foo foo'
831 foofoo Writes 'Foo foo'
832
832
833 (use 'hg help -v -e dodo' to show built-in aliases and global options)
833 (use 'hg help -v -e dodo' to show built-in aliases and global options)
834
834
835 Make sure that '-v -e' prints list of built-in aliases along with
835 Make sure that '-v -e' prints list of built-in aliases along with
836 extension help itself
836 extension help itself
837 $ hg help -v -e dodo
837 $ hg help -v -e dodo
838 dodo extension -
838 dodo extension -
839
839
840 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
840 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
841
841
842 list of commands:
842 list of commands:
843
843
844 dodo Does nothing
844 dodo Does nothing
845 foofoo Writes 'Foo foo'
845 foofoo Writes 'Foo foo'
846
846
847 global options ([+] can be repeated):
847 global options ([+] can be repeated):
848
848
849 -R --repository REPO repository root directory or name of overlay bundle
849 -R --repository REPO repository root directory or name of overlay bundle
850 file
850 file
851 --cwd DIR change working directory
851 --cwd DIR change working directory
852 -y --noninteractive do not prompt, automatically pick the first choice for
852 -y --noninteractive do not prompt, automatically pick the first choice for
853 all prompts
853 all prompts
854 -q --quiet suppress output
854 -q --quiet suppress output
855 -v --verbose enable additional output
855 -v --verbose enable additional output
856 --color TYPE when to colorize (boolean, always, auto, never, or
856 --color TYPE when to colorize (boolean, always, auto, never, or
857 debug)
857 debug)
858 --config CONFIG [+] set/override config option (use 'section.name=value')
858 --config CONFIG [+] set/override config option (use 'section.name=value')
859 --debug enable debugging output
859 --debug enable debugging output
860 --debugger start debugger
860 --debugger start debugger
861 --encoding ENCODE set the charset encoding (default: ascii)
861 --encoding ENCODE set the charset encoding (default: ascii)
862 --encodingmode MODE set the charset encoding mode (default: strict)
862 --encodingmode MODE set the charset encoding mode (default: strict)
863 --traceback always print a traceback on exception
863 --traceback always print a traceback on exception
864 --time time how long the command takes
864 --time time how long the command takes
865 --profile print command execution profile
865 --profile print command execution profile
866 --version output version information and exit
866 --version output version information and exit
867 -h --help display help and exit
867 -h --help display help and exit
868 --hidden consider hidden changesets
868 --hidden consider hidden changesets
869 --pager TYPE when to paginate (boolean, always, auto, or never)
869 --pager TYPE when to paginate (boolean, always, auto, or never)
870 (default: auto)
870 (default: auto)
871
871
872 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
872 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
873 $ hg help -v dodo
873 $ hg help -v dodo
874 hg dodo
874 hg dodo
875
875
876 Does nothing
876 Does nothing
877
877
878 (use 'hg help -e dodo' to show help for the dodo extension)
878 (use 'hg help -e dodo' to show help for the dodo extension)
879
879
880 options:
880 options:
881
881
882 --mq operate on patch repository
882 --mq operate on patch repository
883
883
884 global options ([+] can be repeated):
884 global options ([+] can be repeated):
885
885
886 -R --repository REPO repository root directory or name of overlay bundle
886 -R --repository REPO repository root directory or name of overlay bundle
887 file
887 file
888 --cwd DIR change working directory
888 --cwd DIR change working directory
889 -y --noninteractive do not prompt, automatically pick the first choice for
889 -y --noninteractive do not prompt, automatically pick the first choice for
890 all prompts
890 all prompts
891 -q --quiet suppress output
891 -q --quiet suppress output
892 -v --verbose enable additional output
892 -v --verbose enable additional output
893 --color TYPE when to colorize (boolean, always, auto, never, or
893 --color TYPE when to colorize (boolean, always, auto, never, or
894 debug)
894 debug)
895 --config CONFIG [+] set/override config option (use 'section.name=value')
895 --config CONFIG [+] set/override config option (use 'section.name=value')
896 --debug enable debugging output
896 --debug enable debugging output
897 --debugger start debugger
897 --debugger start debugger
898 --encoding ENCODE set the charset encoding (default: ascii)
898 --encoding ENCODE set the charset encoding (default: ascii)
899 --encodingmode MODE set the charset encoding mode (default: strict)
899 --encodingmode MODE set the charset encoding mode (default: strict)
900 --traceback always print a traceback on exception
900 --traceback always print a traceback on exception
901 --time time how long the command takes
901 --time time how long the command takes
902 --profile print command execution profile
902 --profile print command execution profile
903 --version output version information and exit
903 --version output version information and exit
904 -h --help display help and exit
904 -h --help display help and exit
905 --hidden consider hidden changesets
905 --hidden consider hidden changesets
906 --pager TYPE when to paginate (boolean, always, auto, or never)
906 --pager TYPE when to paginate (boolean, always, auto, or never)
907 (default: auto)
907 (default: auto)
908
908
909 In case when extension name doesn't match any of its commands,
909 In case when extension name doesn't match any of its commands,
910 help message should ask for '-v' to get list of built-in aliases
910 help message should ask for '-v' to get list of built-in aliases
911 along with extension help
911 along with extension help
912 $ cat > $TESTTMP/d/dudu.py <<EOF
912 $ cat > $TESTTMP/d/dudu.py <<EOF
913 > """
913 > """
914 > This is an awesome 'dudu' extension. It does something and
914 > This is an awesome 'dudu' extension. It does something and
915 > also writes 'Beep beep'
915 > also writes 'Beep beep'
916 > """
916 > """
917 > from mercurial import commands, registrar
917 > from mercurial import commands, registrar
918 > cmdtable = {}
918 > cmdtable = {}
919 > command = registrar.command(cmdtable)
919 > command = registrar.command(cmdtable)
920 > @command('something', [], 'hg something')
920 > @command('something', [], 'hg something')
921 > def something(ui, *args, **kwargs):
921 > def something(ui, *args, **kwargs):
922 > """Does something"""
922 > """Does something"""
923 > ui.write("I do something. Yaaay\\n")
923 > ui.write("I do something. Yaaay\\n")
924 > @command('beep', [], 'hg beep')
924 > @command('beep', [], 'hg beep')
925 > def beep(ui, *args, **kwargs):
925 > def beep(ui, *args, **kwargs):
926 > """Writes 'Beep beep'"""
926 > """Writes 'Beep beep'"""
927 > ui.write("Beep beep\\n")
927 > ui.write("Beep beep\\n")
928 > EOF
928 > EOF
929 $ dudupath=$TESTTMP/d/dudu.py
929 $ dudupath=$TESTTMP/d/dudu.py
930
930
931 $ echo "dudu = $dudupath" >> $HGRCPATH
931 $ echo "dudu = $dudupath" >> $HGRCPATH
932
932
933 $ hg help -e dudu
933 $ hg help -e dudu
934 dudu extension -
934 dudu extension -
935
935
936 This is an awesome 'dudu' extension. It does something and also writes 'Beep
936 This is an awesome 'dudu' extension. It does something and also writes 'Beep
937 beep'
937 beep'
938
938
939 list of commands:
939 list of commands:
940
940
941 beep Writes 'Beep beep'
941 beep Writes 'Beep beep'
942 something Does something
942 something Does something
943
943
944 (use 'hg help -v dudu' to show built-in aliases and global options)
944 (use 'hg help -v dudu' to show built-in aliases and global options)
945
945
946 In case when extension name doesn't match any of its commands,
946 In case when extension name doesn't match any of its commands,
947 help options '-v' and '-v -e' should be equivalent
947 help options '-v' and '-v -e' should be equivalent
948 $ hg help -v dudu
948 $ hg help -v dudu
949 dudu extension -
949 dudu extension -
950
950
951 This is an awesome 'dudu' extension. It does something and also writes 'Beep
951 This is an awesome 'dudu' extension. It does something and also writes 'Beep
952 beep'
952 beep'
953
953
954 list of commands:
954 list of commands:
955
955
956 beep Writes 'Beep beep'
956 beep Writes 'Beep beep'
957 something Does something
957 something Does something
958
958
959 global options ([+] can be repeated):
959 global options ([+] can be repeated):
960
960
961 -R --repository REPO repository root directory or name of overlay bundle
961 -R --repository REPO repository root directory or name of overlay bundle
962 file
962 file
963 --cwd DIR change working directory
963 --cwd DIR change working directory
964 -y --noninteractive do not prompt, automatically pick the first choice for
964 -y --noninteractive do not prompt, automatically pick the first choice for
965 all prompts
965 all prompts
966 -q --quiet suppress output
966 -q --quiet suppress output
967 -v --verbose enable additional output
967 -v --verbose enable additional output
968 --color TYPE when to colorize (boolean, always, auto, never, or
968 --color TYPE when to colorize (boolean, always, auto, never, or
969 debug)
969 debug)
970 --config CONFIG [+] set/override config option (use 'section.name=value')
970 --config CONFIG [+] set/override config option (use 'section.name=value')
971 --debug enable debugging output
971 --debug enable debugging output
972 --debugger start debugger
972 --debugger start debugger
973 --encoding ENCODE set the charset encoding (default: ascii)
973 --encoding ENCODE set the charset encoding (default: ascii)
974 --encodingmode MODE set the charset encoding mode (default: strict)
974 --encodingmode MODE set the charset encoding mode (default: strict)
975 --traceback always print a traceback on exception
975 --traceback always print a traceback on exception
976 --time time how long the command takes
976 --time time how long the command takes
977 --profile print command execution profile
977 --profile print command execution profile
978 --version output version information and exit
978 --version output version information and exit
979 -h --help display help and exit
979 -h --help display help and exit
980 --hidden consider hidden changesets
980 --hidden consider hidden changesets
981 --pager TYPE when to paginate (boolean, always, auto, or never)
981 --pager TYPE when to paginate (boolean, always, auto, or never)
982 (default: auto)
982 (default: auto)
983
983
984 $ hg help -v -e dudu
984 $ hg help -v -e dudu
985 dudu extension -
985 dudu extension -
986
986
987 This is an awesome 'dudu' extension. It does something and also writes 'Beep
987 This is an awesome 'dudu' extension. It does something and also writes 'Beep
988 beep'
988 beep'
989
989
990 list of commands:
990 list of commands:
991
991
992 beep Writes 'Beep beep'
992 beep Writes 'Beep beep'
993 something Does something
993 something Does something
994
994
995 global options ([+] can be repeated):
995 global options ([+] can be repeated):
996
996
997 -R --repository REPO repository root directory or name of overlay bundle
997 -R --repository REPO repository root directory or name of overlay bundle
998 file
998 file
999 --cwd DIR change working directory
999 --cwd DIR change working directory
1000 -y --noninteractive do not prompt, automatically pick the first choice for
1000 -y --noninteractive do not prompt, automatically pick the first choice for
1001 all prompts
1001 all prompts
1002 -q --quiet suppress output
1002 -q --quiet suppress output
1003 -v --verbose enable additional output
1003 -v --verbose enable additional output
1004 --color TYPE when to colorize (boolean, always, auto, never, or
1004 --color TYPE when to colorize (boolean, always, auto, never, or
1005 debug)
1005 debug)
1006 --config CONFIG [+] set/override config option (use 'section.name=value')
1006 --config CONFIG [+] set/override config option (use 'section.name=value')
1007 --debug enable debugging output
1007 --debug enable debugging output
1008 --debugger start debugger
1008 --debugger start debugger
1009 --encoding ENCODE set the charset encoding (default: ascii)
1009 --encoding ENCODE set the charset encoding (default: ascii)
1010 --encodingmode MODE set the charset encoding mode (default: strict)
1010 --encodingmode MODE set the charset encoding mode (default: strict)
1011 --traceback always print a traceback on exception
1011 --traceback always print a traceback on exception
1012 --time time how long the command takes
1012 --time time how long the command takes
1013 --profile print command execution profile
1013 --profile print command execution profile
1014 --version output version information and exit
1014 --version output version information and exit
1015 -h --help display help and exit
1015 -h --help display help and exit
1016 --hidden consider hidden changesets
1016 --hidden consider hidden changesets
1017 --pager TYPE when to paginate (boolean, always, auto, or never)
1017 --pager TYPE when to paginate (boolean, always, auto, or never)
1018 (default: auto)
1018 (default: auto)
1019
1019
1020 Disabled extension commands:
1020 Disabled extension commands:
1021
1021
1022 $ ORGHGRCPATH=$HGRCPATH
1022 $ ORGHGRCPATH=$HGRCPATH
1023 $ HGRCPATH=
1023 $ HGRCPATH=
1024 $ export HGRCPATH
1024 $ export HGRCPATH
1025 $ hg help email
1025 $ hg help email
1026 'email' is provided by the following extension:
1026 'email' is provided by the following extension:
1027
1027
1028 patchbomb command to send changesets as (a series of) patch emails
1028 patchbomb command to send changesets as (a series of) patch emails
1029
1029
1030 (use 'hg help extensions' for information on enabling extensions)
1030 (use 'hg help extensions' for information on enabling extensions)
1031
1031
1032
1032
1033 $ hg qdel
1033 $ hg qdel
1034 hg: unknown command 'qdel'
1034 hg: unknown command 'qdel'
1035 'qdelete' is provided by the following extension:
1035 'qdelete' is provided by the following extension:
1036
1036
1037 mq manage a stack of patches
1037 mq manage a stack of patches
1038
1038
1039 (use 'hg help extensions' for information on enabling extensions)
1039 (use 'hg help extensions' for information on enabling extensions)
1040 [255]
1040 [255]
1041
1041
1042
1042
1043 $ hg churn
1043 $ hg churn
1044 hg: unknown command 'churn'
1044 hg: unknown command 'churn'
1045 'churn' is provided by the following extension:
1045 'churn' is provided by the following extension:
1046
1046
1047 churn command to display statistics about repository history
1047 churn command to display statistics about repository history
1048
1048
1049 (use 'hg help extensions' for information on enabling extensions)
1049 (use 'hg help extensions' for information on enabling extensions)
1050 [255]
1050 [255]
1051
1051
1052
1052
1053
1053
1054 Disabled extensions:
1054 Disabled extensions:
1055
1055
1056 $ hg help churn
1056 $ hg help churn
1057 churn extension - command to display statistics about repository history
1057 churn extension - command to display statistics about repository history
1058
1058
1059 (use 'hg help extensions' for information on enabling extensions)
1059 (use 'hg help extensions' for information on enabling extensions)
1060
1060
1061 $ hg help patchbomb
1061 $ hg help patchbomb
1062 patchbomb extension - command to send changesets as (a series of) patch emails
1062 patchbomb extension - command to send changesets as (a series of) patch emails
1063
1063
1064 The series is started off with a "[PATCH 0 of N]" introduction, which
1064 The series is started off with a "[PATCH 0 of N]" introduction, which
1065 describes the series as a whole.
1065 describes the series as a whole.
1066
1066
1067 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1067 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1068 line of the changeset description as the subject text. The message contains
1068 line of the changeset description as the subject text. The message contains
1069 two or three body parts:
1069 two or three body parts:
1070
1070
1071 - The changeset description.
1071 - The changeset description.
1072 - [Optional] The result of running diffstat on the patch.
1072 - [Optional] The result of running diffstat on the patch.
1073 - The patch itself, as generated by 'hg export'.
1073 - The patch itself, as generated by 'hg export'.
1074
1074
1075 Each message refers to the first in the series using the In-Reply-To and
1075 Each message refers to the first in the series using the In-Reply-To and
1076 References headers, so they will show up as a sequence in threaded mail and
1076 References headers, so they will show up as a sequence in threaded mail and
1077 news readers, and in mail archives.
1077 news readers, and in mail archives.
1078
1078
1079 To configure other defaults, add a section like this to your configuration
1079 To configure other defaults, add a section like this to your configuration
1080 file:
1080 file:
1081
1081
1082 [email]
1082 [email]
1083 from = My Name <my@email>
1083 from = My Name <my@email>
1084 to = recipient1, recipient2, ...
1084 to = recipient1, recipient2, ...
1085 cc = cc1, cc2, ...
1085 cc = cc1, cc2, ...
1086 bcc = bcc1, bcc2, ...
1086 bcc = bcc1, bcc2, ...
1087 reply-to = address1, address2, ...
1087 reply-to = address1, address2, ...
1088
1088
1089 Use "[patchbomb]" as configuration section name if you need to override global
1089 Use "[patchbomb]" as configuration section name if you need to override global
1090 "[email]" address settings.
1090 "[email]" address settings.
1091
1091
1092 Then you can use the 'hg email' command to mail a series of changesets as a
1092 Then you can use the 'hg email' command to mail a series of changesets as a
1093 patchbomb.
1093 patchbomb.
1094
1094
1095 You can also either configure the method option in the email section to be a
1095 You can also either configure the method option in the email section to be a
1096 sendmail compatible mailer or fill out the [smtp] section so that the
1096 sendmail compatible mailer or fill out the [smtp] section so that the
1097 patchbomb extension can automatically send patchbombs directly from the
1097 patchbomb extension can automatically send patchbombs directly from the
1098 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1098 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1099
1099
1100 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1100 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1101 supply one via configuration or the command line. You can override this to
1101 supply one via configuration or the command line. You can override this to
1102 never prompt by configuring an empty value:
1102 never prompt by configuring an empty value:
1103
1103
1104 [email]
1104 [email]
1105 cc =
1105 cc =
1106
1106
1107 You can control the default inclusion of an introduction message with the
1107 You can control the default inclusion of an introduction message with the
1108 "patchbomb.intro" configuration option. The configuration is always
1108 "patchbomb.intro" configuration option. The configuration is always
1109 overwritten by command line flags like --intro and --desc:
1109 overwritten by command line flags like --intro and --desc:
1110
1110
1111 [patchbomb]
1111 [patchbomb]
1112 intro=auto # include introduction message if more than 1 patch (default)
1112 intro=auto # include introduction message if more than 1 patch (default)
1113 intro=never # never include an introduction message
1113 intro=never # never include an introduction message
1114 intro=always # always include an introduction message
1114 intro=always # always include an introduction message
1115
1115
1116 You can specify a template for flags to be added in subject prefixes. Flags
1116 You can specify a template for flags to be added in subject prefixes. Flags
1117 specified by --flag option are exported as "{flags}" keyword:
1117 specified by --flag option are exported as "{flags}" keyword:
1118
1118
1119 [patchbomb]
1119 [patchbomb]
1120 flagtemplate = "{separate(' ',
1120 flagtemplate = "{separate(' ',
1121 ifeq(branch, 'default', '', branch|upper),
1121 ifeq(branch, 'default', '', branch|upper),
1122 flags)}"
1122 flags)}"
1123
1123
1124 You can set patchbomb to always ask for confirmation by setting
1124 You can set patchbomb to always ask for confirmation by setting
1125 "patchbomb.confirm" to true.
1125 "patchbomb.confirm" to true.
1126
1126
1127 (use 'hg help extensions' for information on enabling extensions)
1127 (use 'hg help extensions' for information on enabling extensions)
1128
1128
1129
1129
1130 Broken disabled extension and command:
1130 Broken disabled extension and command:
1131
1131
1132 $ mkdir hgext
1132 $ mkdir hgext
1133 $ echo > hgext/__init__.py
1133 $ echo > hgext/__init__.py
1134 $ cat > hgext/broken.py <<EOF
1134 $ cat > hgext/broken.py <<EOF
1135 > "broken extension'
1135 > "broken extension'
1136 > EOF
1136 > EOF
1137 $ cat > path.py <<EOF
1137 $ cat > path.py <<EOF
1138 > import os, sys
1138 > import os, sys
1139 > sys.path.insert(0, os.environ['HGEXTPATH'])
1139 > sys.path.insert(0, os.environ['HGEXTPATH'])
1140 > EOF
1140 > EOF
1141 $ HGEXTPATH=`pwd`
1141 $ HGEXTPATH=`pwd`
1142 $ export HGEXTPATH
1142 $ export HGEXTPATH
1143
1143
1144 $ hg --config extensions.path=./path.py help broken
1144 $ hg --config extensions.path=./path.py help broken
1145 broken extension - (no help text available)
1145 broken extension - (no help text available)
1146
1146
1147 (use 'hg help extensions' for information on enabling extensions)
1147 (use 'hg help extensions' for information on enabling extensions)
1148
1148
1149
1149
1150 $ cat > hgext/forest.py <<EOF
1150 $ cat > hgext/forest.py <<EOF
1151 > cmdtable = None
1151 > cmdtable = None
1152 > EOF
1152 > EOF
1153 $ hg --config extensions.path=./path.py help foo > /dev/null
1153 $ hg --config extensions.path=./path.py help foo > /dev/null
1154 warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
1154 warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
1155 abort: no such help topic: foo
1155 abort: no such help topic: foo
1156 (try 'hg help --keyword foo')
1156 (try 'hg help --keyword foo')
1157 [255]
1157 [255]
1158
1158
1159 $ cat > throw.py <<EOF
1159 $ cat > throw.py <<EOF
1160 > from mercurial import commands, registrar, util
1160 > from mercurial import commands, registrar, util
1161 > cmdtable = {}
1161 > cmdtable = {}
1162 > command = registrar.command(cmdtable)
1162 > command = registrar.command(cmdtable)
1163 > class Bogon(Exception): pass
1163 > class Bogon(Exception): pass
1164 > @command('throw', [], 'hg throw', norepo=True)
1164 > @command('throw', [], 'hg throw', norepo=True)
1165 > def throw(ui, **opts):
1165 > def throw(ui, **opts):
1166 > """throws an exception"""
1166 > """throws an exception"""
1167 > raise Bogon()
1167 > raise Bogon()
1168 > EOF
1168 > EOF
1169
1169
1170 No declared supported version, extension complains:
1170 No declared supported version, extension complains:
1171 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1171 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1172 ** Unknown exception encountered with possibly-broken third-party extension throw
1172 ** Unknown exception encountered with possibly-broken third-party extension throw
1173 ** which supports versions unknown of Mercurial.
1173 ** which supports versions unknown of Mercurial.
1174 ** Please disable throw and try your action again.
1174 ** Please disable throw and try your action again.
1175 ** If that fixes the bug please report it to the extension author.
1175 ** If that fixes the bug please report it to the extension author.
1176 ** Python * (glob)
1176 ** Python * (glob)
1177 ** Mercurial Distributed SCM * (glob)
1177 ** Mercurial Distributed SCM * (glob)
1178 ** Extensions loaded: throw
1178 ** Extensions loaded: throw
1179
1179
1180 empty declaration of supported version, extension complains:
1180 empty declaration of supported version, extension complains:
1181 $ echo "testedwith = ''" >> throw.py
1181 $ echo "testedwith = ''" >> throw.py
1182 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1182 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1183 ** Unknown exception encountered with possibly-broken third-party extension throw
1183 ** Unknown exception encountered with possibly-broken third-party extension throw
1184 ** which supports versions unknown of Mercurial.
1184 ** which supports versions unknown of Mercurial.
1185 ** Please disable throw and try your action again.
1185 ** Please disable throw and try your action again.
1186 ** If that fixes the bug please report it to the extension author.
1186 ** If that fixes the bug please report it to the extension author.
1187 ** Python * (glob)
1187 ** Python * (glob)
1188 ** Mercurial Distributed SCM (*) (glob)
1188 ** Mercurial Distributed SCM (*) (glob)
1189 ** Extensions loaded: throw
1189 ** Extensions loaded: throw
1190
1190
1191 If the extension specifies a buglink, show that:
1191 If the extension specifies a buglink, show that:
1192 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1192 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1193 $ rm -f throw.pyc throw.pyo
1193 $ rm -f throw.pyc throw.pyo
1194 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1194 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1195 ** Unknown exception encountered with possibly-broken third-party extension throw
1195 ** Unknown exception encountered with possibly-broken third-party extension throw
1196 ** which supports versions unknown of Mercurial.
1196 ** which supports versions unknown of Mercurial.
1197 ** Please disable throw and try your action again.
1197 ** Please disable throw and try your action again.
1198 ** If that fixes the bug please report it to http://example.com/bts
1198 ** If that fixes the bug please report it to http://example.com/bts
1199 ** Python * (glob)
1199 ** Python * (glob)
1200 ** Mercurial Distributed SCM (*) (glob)
1200 ** Mercurial Distributed SCM (*) (glob)
1201 ** Extensions loaded: throw
1201 ** Extensions loaded: throw
1202
1202
1203 If the extensions declare outdated versions, accuse the older extension first:
1203 If the extensions declare outdated versions, accuse the older extension first:
1204 $ echo "from mercurial import util" >> older.py
1204 $ echo "from mercurial import util" >> older.py
1205 $ echo "util.version = lambda:'2.2'" >> older.py
1205 $ echo "util.version = lambda:'2.2'" >> older.py
1206 $ echo "testedwith = '1.9.3'" >> older.py
1206 $ echo "testedwith = '1.9.3'" >> older.py
1207 $ echo "testedwith = '2.1.1'" >> throw.py
1207 $ echo "testedwith = '2.1.1'" >> throw.py
1208 $ rm -f throw.pyc throw.pyo
1208 $ rm -f throw.pyc throw.pyo
1209 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1209 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1210 > throw 2>&1 | egrep '^\*\*'
1210 > throw 2>&1 | egrep '^\*\*'
1211 ** Unknown exception encountered with possibly-broken third-party extension older
1211 ** Unknown exception encountered with possibly-broken third-party extension older
1212 ** which supports versions 1.9 of Mercurial.
1212 ** which supports versions 1.9 of Mercurial.
1213 ** Please disable older and try your action again.
1213 ** Please disable older and try your action again.
1214 ** If that fixes the bug please report it to the extension author.
1214 ** If that fixes the bug please report it to the extension author.
1215 ** Python * (glob)
1215 ** Python * (glob)
1216 ** Mercurial Distributed SCM (version 2.2)
1216 ** Mercurial Distributed SCM (version 2.2)
1217 ** Extensions loaded: throw, older
1217 ** Extensions loaded: throw, older
1218
1218
1219 One extension only tested with older, one only with newer versions:
1219 One extension only tested with older, one only with newer versions:
1220 $ echo "util.version = lambda:'2.1'" >> older.py
1220 $ echo "util.version = lambda:'2.1'" >> older.py
1221 $ rm -f older.pyc older.pyo
1221 $ rm -f older.pyc older.pyo
1222 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1222 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1223 > throw 2>&1 | egrep '^\*\*'
1223 > throw 2>&1 | egrep '^\*\*'
1224 ** Unknown exception encountered with possibly-broken third-party extension older
1224 ** Unknown exception encountered with possibly-broken third-party extension older
1225 ** which supports versions 1.9 of Mercurial.
1225 ** which supports versions 1.9 of Mercurial.
1226 ** Please disable older and try your action again.
1226 ** Please disable older and try your action again.
1227 ** If that fixes the bug please report it to the extension author.
1227 ** If that fixes the bug please report it to the extension author.
1228 ** Python * (glob)
1228 ** Python * (glob)
1229 ** Mercurial Distributed SCM (version 2.1)
1229 ** Mercurial Distributed SCM (version 2.1)
1230 ** Extensions loaded: throw, older
1230 ** Extensions loaded: throw, older
1231
1231
1232 Older extension is tested with current version, the other only with newer:
1232 Older extension is tested with current version, the other only with newer:
1233 $ echo "util.version = lambda:'1.9.3'" >> older.py
1233 $ echo "util.version = lambda:'1.9.3'" >> older.py
1234 $ rm -f older.pyc older.pyo
1234 $ rm -f older.pyc older.pyo
1235 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1235 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1236 > throw 2>&1 | egrep '^\*\*'
1236 > throw 2>&1 | egrep '^\*\*'
1237 ** Unknown exception encountered with possibly-broken third-party extension throw
1237 ** Unknown exception encountered with possibly-broken third-party extension throw
1238 ** which supports versions 2.1 of Mercurial.
1238 ** which supports versions 2.1 of Mercurial.
1239 ** Please disable throw and try your action again.
1239 ** Please disable throw and try your action again.
1240 ** If that fixes the bug please report it to http://example.com/bts
1240 ** If that fixes the bug please report it to http://example.com/bts
1241 ** Python * (glob)
1241 ** Python * (glob)
1242 ** Mercurial Distributed SCM (version 1.9.3)
1242 ** Mercurial Distributed SCM (version 1.9.3)
1243 ** Extensions loaded: throw, older
1243 ** Extensions loaded: throw, older
1244
1244
1245 Ability to point to a different point
1245 Ability to point to a different point
1246 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1246 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1247 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1247 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1248 ** unknown exception encountered, please report by visiting
1248 ** unknown exception encountered, please report by visiting
1249 ** Your Local Goat Lenders
1249 ** Your Local Goat Lenders
1250 ** Python * (glob)
1250 ** Python * (glob)
1251 ** Mercurial Distributed SCM (*) (glob)
1251 ** Mercurial Distributed SCM (*) (glob)
1252 ** Extensions loaded: throw, older
1252 ** Extensions loaded: throw, older
1253
1253
1254 Declare the version as supporting this hg version, show regular bts link:
1254 Declare the version as supporting this hg version, show regular bts link:
1255 $ hgver=`hg debuginstall -T '{hgver}'`
1255 $ hgver=`hg debuginstall -T '{hgver}'`
1256 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1256 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1257 $ if [ -z "$hgver" ]; then
1257 $ if [ -z "$hgver" ]; then
1258 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1258 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1259 > fi
1259 > fi
1260 $ rm -f throw.pyc throw.pyo
1260 $ rm -f throw.pyc throw.pyo
1261 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1261 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1262 ** unknown exception encountered, please report by visiting
1262 ** unknown exception encountered, please report by visiting
1263 ** https://mercurial-scm.org/wiki/BugTracker
1263 ** https://mercurial-scm.org/wiki/BugTracker
1264 ** Python * (glob)
1264 ** Python * (glob)
1265 ** Mercurial Distributed SCM (*) (glob)
1265 ** Mercurial Distributed SCM (*) (glob)
1266 ** Extensions loaded: throw
1266 ** Extensions loaded: throw
1267
1267
1268 Patch version is ignored during compatibility check
1268 Patch version is ignored during compatibility check
1269 $ echo "testedwith = '3.2'" >> throw.py
1269 $ echo "testedwith = '3.2'" >> throw.py
1270 $ echo "util.version = lambda:'3.2.2'" >> throw.py
1270 $ echo "util.version = lambda:'3.2.2'" >> throw.py
1271 $ rm -f throw.pyc throw.pyo
1271 $ rm -f throw.pyc throw.pyo
1272 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1272 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1273 ** unknown exception encountered, please report by visiting
1273 ** unknown exception encountered, please report by visiting
1274 ** https://mercurial-scm.org/wiki/BugTracker
1274 ** https://mercurial-scm.org/wiki/BugTracker
1275 ** Python * (glob)
1275 ** Python * (glob)
1276 ** Mercurial Distributed SCM (*) (glob)
1276 ** Mercurial Distributed SCM (*) (glob)
1277 ** Extensions loaded: throw
1277 ** Extensions loaded: throw
1278
1278
1279 Test version number support in 'hg version':
1279 Test version number support in 'hg version':
1280 $ echo '__version__ = (1, 2, 3)' >> throw.py
1280 $ echo '__version__ = (1, 2, 3)' >> throw.py
1281 $ rm -f throw.pyc throw.pyo
1281 $ rm -f throw.pyc throw.pyo
1282 $ hg version -v
1282 $ hg version -v
1283 Mercurial Distributed SCM (version *) (glob)
1283 Mercurial Distributed SCM (version *) (glob)
1284 (see https://mercurial-scm.org for more information)
1284 (see https://mercurial-scm.org for more information)
1285
1285
1286 Copyright (C) 2005-* Matt Mackall and others (glob)
1286 Copyright (C) 2005-* Matt Mackall and others (glob)
1287 This is free software; see the source for copying conditions. There is NO
1287 This is free software; see the source for copying conditions. There is NO
1288 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1288 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1289
1289
1290 Enabled extensions:
1290 Enabled extensions:
1291
1291
1292
1292
1293 $ hg version -v --config extensions.throw=throw.py
1293 $ hg version -v --config extensions.throw=throw.py
1294 Mercurial Distributed SCM (version *) (glob)
1294 Mercurial Distributed SCM (version *) (glob)
1295 (see https://mercurial-scm.org for more information)
1295 (see https://mercurial-scm.org for more information)
1296
1296
1297 Copyright (C) 2005-* Matt Mackall and others (glob)
1297 Copyright (C) 2005-* Matt Mackall and others (glob)
1298 This is free software; see the source for copying conditions. There is NO
1298 This is free software; see the source for copying conditions. There is NO
1299 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1299 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1300
1300
1301 Enabled extensions:
1301 Enabled extensions:
1302
1302
1303 throw external 1.2.3
1303 throw external 1.2.3
1304 $ echo 'getversion = lambda: "1.twentythree"' >> throw.py
1304 $ echo 'getversion = lambda: "1.twentythree"' >> throw.py
1305 $ rm -f throw.pyc throw.pyo
1305 $ rm -f throw.pyc throw.pyo
1306 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1306 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1307 Mercurial Distributed SCM (version *) (glob)
1307 Mercurial Distributed SCM (version *) (glob)
1308 (see https://mercurial-scm.org for more information)
1308 (see https://mercurial-scm.org for more information)
1309
1309
1310 Copyright (C) 2005-* Matt Mackall and others (glob)
1310 Copyright (C) 2005-* Matt Mackall and others (glob)
1311 This is free software; see the source for copying conditions. There is NO
1311 This is free software; see the source for copying conditions. There is NO
1312 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1312 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1313
1313
1314 Enabled extensions:
1314 Enabled extensions:
1315
1315
1316 throw external 1.twentythree
1316 throw external 1.twentythree
1317 strip internal
1317 strip internal
1318
1318
1319 $ hg version -q --config extensions.throw=throw.py
1319 $ hg version -q --config extensions.throw=throw.py
1320 Mercurial Distributed SCM (version *) (glob)
1320 Mercurial Distributed SCM (version *) (glob)
1321
1321
1322 Test JSON output of version:
1322 Test JSON output of version:
1323
1323
1324 $ hg version -Tjson
1324 $ hg version -Tjson
1325 [
1325 [
1326 {
1326 {
1327 "extensions": [],
1327 "extensions": [],
1328 "ver": "*" (glob)
1328 "ver": "*" (glob)
1329 }
1329 }
1330 ]
1330 ]
1331
1331
1332 $ hg version --config extensions.throw=throw.py -Tjson
1332 $ hg version --config extensions.throw=throw.py -Tjson
1333 [
1333 [
1334 {
1334 {
1335 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1335 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1336 "ver": "3.2.2"
1336 "ver": "3.2.2"
1337 }
1337 }
1338 ]
1338 ]
1339
1339
1340 $ hg version --config extensions.strip= -Tjson
1340 $ hg version --config extensions.strip= -Tjson
1341 [
1341 [
1342 {
1342 {
1343 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1343 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1344 "ver": "*" (glob)
1344 "ver": "*" (glob)
1345 }
1345 }
1346 ]
1346 ]
1347
1347
1348 Test template output of version:
1348 Test template output of version:
1349
1349
1350 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1350 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1351 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1351 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1352 throw 1.twentythree (external)
1352 throw 1.twentythree (external)
1353 strip (internal)
1353 strip (internal)
1354
1354
1355 Refuse to load extensions with minimum version requirements
1355 Refuse to load extensions with minimum version requirements
1356
1356
1357 $ cat > minversion1.py << EOF
1357 $ cat > minversion1.py << EOF
1358 > from mercurial import util
1358 > from mercurial import util
1359 > util.version = lambda: '3.5.2'
1359 > util.version = lambda: '3.5.2'
1360 > minimumhgversion = '3.6'
1360 > minimumhgversion = '3.6'
1361 > EOF
1361 > EOF
1362 $ hg --config extensions.minversion=minversion1.py version
1362 $ hg --config extensions.minversion=minversion1.py version
1363 (third party extension minversion requires version 3.6 or newer of Mercurial; disabling)
1363 (third party extension minversion requires version 3.6 or newer of Mercurial; disabling)
1364 Mercurial Distributed SCM (version 3.5.2)
1364 Mercurial Distributed SCM (version 3.5.2)
1365 (see https://mercurial-scm.org for more information)
1365 (see https://mercurial-scm.org for more information)
1366
1366
1367 Copyright (C) 2005-* Matt Mackall and others (glob)
1367 Copyright (C) 2005-* Matt Mackall and others (glob)
1368 This is free software; see the source for copying conditions. There is NO
1368 This is free software; see the source for copying conditions. There is NO
1369 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1369 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1370
1370
1371 $ cat > minversion2.py << EOF
1371 $ cat > minversion2.py << EOF
1372 > from mercurial import util
1372 > from mercurial import util
1373 > util.version = lambda: '3.6'
1373 > util.version = lambda: '3.6'
1374 > minimumhgversion = '3.7'
1374 > minimumhgversion = '3.7'
1375 > EOF
1375 > EOF
1376 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1376 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1377 (third party extension minversion requires version 3.7 or newer of Mercurial; disabling)
1377 (third party extension minversion requires version 3.7 or newer of Mercurial; disabling)
1378
1378
1379 Can load version that is only off by point release
1379 Can load version that is only off by point release
1380
1380
1381 $ cat > minversion2.py << EOF
1381 $ cat > minversion2.py << EOF
1382 > from mercurial import util
1382 > from mercurial import util
1383 > util.version = lambda: '3.6.1'
1383 > util.version = lambda: '3.6.1'
1384 > minimumhgversion = '3.6'
1384 > minimumhgversion = '3.6'
1385 > EOF
1385 > EOF
1386 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1386 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1387 [1]
1387 [1]
1388
1388
1389 Can load minimum version identical to current
1389 Can load minimum version identical to current
1390
1390
1391 $ cat > minversion3.py << EOF
1391 $ cat > minversion3.py << EOF
1392 > from mercurial import util
1392 > from mercurial import util
1393 > util.version = lambda: '3.5'
1393 > util.version = lambda: '3.5'
1394 > minimumhgversion = '3.5'
1394 > minimumhgversion = '3.5'
1395 > EOF
1395 > EOF
1396 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1396 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1397 [1]
1397 [1]
1398
1398
1399 Restore HGRCPATH
1399 Restore HGRCPATH
1400
1400
1401 $ HGRCPATH=$ORGHGRCPATH
1401 $ HGRCPATH=$ORGHGRCPATH
1402 $ export HGRCPATH
1402 $ export HGRCPATH
1403
1403
1404 Commands handling multiple repositories at a time should invoke only
1404 Commands handling multiple repositories at a time should invoke only
1405 "reposetup()" of extensions enabling in the target repository.
1405 "reposetup()" of extensions enabling in the target repository.
1406
1406
1407 $ mkdir reposetup-test
1407 $ mkdir reposetup-test
1408 $ cd reposetup-test
1408 $ cd reposetup-test
1409
1409
1410 $ cat > $TESTTMP/reposetuptest.py <<EOF
1410 $ cat > $TESTTMP/reposetuptest.py <<EOF
1411 > from mercurial import extensions
1411 > from mercurial import extensions
1412 > def reposetup(ui, repo):
1412 > def reposetup(ui, repo):
1413 > ui.write('reposetup() for %s\n' % (repo.root))
1413 > ui.write('reposetup() for %s\n' % (repo.root))
1414 > ui.flush()
1414 > ui.flush()
1415 > EOF
1415 > EOF
1416 $ hg init src
1416 $ hg init src
1417 $ echo a > src/a
1417 $ echo a > src/a
1418 $ hg -R src commit -Am '#0 at src/a'
1418 $ hg -R src commit -Am '#0 at src/a'
1419 adding a
1419 adding a
1420 $ echo '[extensions]' >> src/.hg/hgrc
1420 $ echo '[extensions]' >> src/.hg/hgrc
1421 $ echo '# enable extension locally' >> src/.hg/hgrc
1421 $ echo '# enable extension locally' >> src/.hg/hgrc
1422 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1422 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1423 $ hg -R src status
1423 $ hg -R src status
1424 reposetup() for $TESTTMP/reposetup-test/src (glob)
1424 reposetup() for $TESTTMP/reposetup-test/src (glob)
1425
1425
1426 $ hg clone -U src clone-dst1
1426 $ hg clone -U src clone-dst1
1427 reposetup() for $TESTTMP/reposetup-test/src (glob)
1427 reposetup() for $TESTTMP/reposetup-test/src (glob)
1428 $ hg init push-dst1
1428 $ hg init push-dst1
1429 $ hg -q -R src push push-dst1
1429 $ hg -q -R src push push-dst1
1430 reposetup() for $TESTTMP/reposetup-test/src (glob)
1430 reposetup() for $TESTTMP/reposetup-test/src (glob)
1431 $ hg init pull-src1
1431 $ hg init pull-src1
1432 $ hg -q -R pull-src1 pull src
1432 $ hg -q -R pull-src1 pull src
1433 reposetup() for $TESTTMP/reposetup-test/src (glob)
1433 reposetup() for $TESTTMP/reposetup-test/src (glob)
1434
1434
1435 $ cat <<EOF >> $HGRCPATH
1435 $ cat <<EOF >> $HGRCPATH
1436 > [extensions]
1436 > [extensions]
1437 > # disable extension globally and explicitly
1437 > # disable extension globally and explicitly
1438 > reposetuptest = !
1438 > reposetuptest = !
1439 > EOF
1439 > EOF
1440 $ hg clone -U src clone-dst2
1440 $ hg clone -U src clone-dst2
1441 reposetup() for $TESTTMP/reposetup-test/src (glob)
1441 reposetup() for $TESTTMP/reposetup-test/src (glob)
1442 $ hg init push-dst2
1442 $ hg init push-dst2
1443 $ hg -q -R src push push-dst2
1443 $ hg -q -R src push push-dst2
1444 reposetup() for $TESTTMP/reposetup-test/src (glob)
1444 reposetup() for $TESTTMP/reposetup-test/src (glob)
1445 $ hg init pull-src2
1445 $ hg init pull-src2
1446 $ hg -q -R pull-src2 pull src
1446 $ hg -q -R pull-src2 pull src
1447 reposetup() for $TESTTMP/reposetup-test/src (glob)
1447 reposetup() for $TESTTMP/reposetup-test/src (glob)
1448
1448
1449 $ cat <<EOF >> $HGRCPATH
1449 $ cat <<EOF >> $HGRCPATH
1450 > [extensions]
1450 > [extensions]
1451 > # enable extension globally
1451 > # enable extension globally
1452 > reposetuptest = $TESTTMP/reposetuptest.py
1452 > reposetuptest = $TESTTMP/reposetuptest.py
1453 > EOF
1453 > EOF
1454 $ hg clone -U src clone-dst3
1454 $ hg clone -U src clone-dst3
1455 reposetup() for $TESTTMP/reposetup-test/src (glob)
1455 reposetup() for $TESTTMP/reposetup-test/src (glob)
1456 reposetup() for $TESTTMP/reposetup-test/clone-dst3 (glob)
1456 reposetup() for $TESTTMP/reposetup-test/clone-dst3 (glob)
1457 $ hg init push-dst3
1457 $ hg init push-dst3
1458 reposetup() for $TESTTMP/reposetup-test/push-dst3 (glob)
1458 reposetup() for $TESTTMP/reposetup-test/push-dst3 (glob)
1459 $ hg -q -R src push push-dst3
1459 $ hg -q -R src push push-dst3
1460 reposetup() for $TESTTMP/reposetup-test/src (glob)
1460 reposetup() for $TESTTMP/reposetup-test/src (glob)
1461 reposetup() for $TESTTMP/reposetup-test/push-dst3 (glob)
1461 reposetup() for $TESTTMP/reposetup-test/push-dst3 (glob)
1462 $ hg init pull-src3
1462 $ hg init pull-src3
1463 reposetup() for $TESTTMP/reposetup-test/pull-src3 (glob)
1463 reposetup() for $TESTTMP/reposetup-test/pull-src3 (glob)
1464 $ hg -q -R pull-src3 pull src
1464 $ hg -q -R pull-src3 pull src
1465 reposetup() for $TESTTMP/reposetup-test/pull-src3 (glob)
1465 reposetup() for $TESTTMP/reposetup-test/pull-src3 (glob)
1466 reposetup() for $TESTTMP/reposetup-test/src (glob)
1466 reposetup() for $TESTTMP/reposetup-test/src (glob)
1467
1467
1468 $ echo '[extensions]' >> src/.hg/hgrc
1468 $ echo '[extensions]' >> src/.hg/hgrc
1469 $ echo '# disable extension locally' >> src/.hg/hgrc
1469 $ echo '# disable extension locally' >> src/.hg/hgrc
1470 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1470 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1471 $ hg clone -U src clone-dst4
1471 $ hg clone -U src clone-dst4
1472 reposetup() for $TESTTMP/reposetup-test/clone-dst4 (glob)
1472 reposetup() for $TESTTMP/reposetup-test/clone-dst4 (glob)
1473 $ hg init push-dst4
1473 $ hg init push-dst4
1474 reposetup() for $TESTTMP/reposetup-test/push-dst4 (glob)
1474 reposetup() for $TESTTMP/reposetup-test/push-dst4 (glob)
1475 $ hg -q -R src push push-dst4
1475 $ hg -q -R src push push-dst4
1476 reposetup() for $TESTTMP/reposetup-test/push-dst4 (glob)
1476 reposetup() for $TESTTMP/reposetup-test/push-dst4 (glob)
1477 $ hg init pull-src4
1477 $ hg init pull-src4
1478 reposetup() for $TESTTMP/reposetup-test/pull-src4 (glob)
1478 reposetup() for $TESTTMP/reposetup-test/pull-src4 (glob)
1479 $ hg -q -R pull-src4 pull src
1479 $ hg -q -R pull-src4 pull src
1480 reposetup() for $TESTTMP/reposetup-test/pull-src4 (glob)
1480 reposetup() for $TESTTMP/reposetup-test/pull-src4 (glob)
1481
1481
1482 disabling in command line overlays with all configuration
1482 disabling in command line overlays with all configuration
1483 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1483 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1484 $ hg --config extensions.reposetuptest=! init push-dst5
1484 $ hg --config extensions.reposetuptest=! init push-dst5
1485 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1485 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1486 $ hg --config extensions.reposetuptest=! init pull-src5
1486 $ hg --config extensions.reposetuptest=! init pull-src5
1487 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1487 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1488
1488
1489 $ cat <<EOF >> $HGRCPATH
1489 $ cat <<EOF >> $HGRCPATH
1490 > [extensions]
1490 > [extensions]
1491 > # disable extension globally and explicitly
1491 > # disable extension globally and explicitly
1492 > reposetuptest = !
1492 > reposetuptest = !
1493 > EOF
1493 > EOF
1494 $ hg init parent
1494 $ hg init parent
1495 $ hg init parent/sub1
1495 $ hg init parent/sub1
1496 $ echo 1 > parent/sub1/1
1496 $ echo 1 > parent/sub1/1
1497 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1497 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1498 adding 1
1498 adding 1
1499 $ hg init parent/sub2
1499 $ hg init parent/sub2
1500 $ hg init parent/sub2/sub21
1500 $ hg init parent/sub2/sub21
1501 $ echo 21 > parent/sub2/sub21/21
1501 $ echo 21 > parent/sub2/sub21/21
1502 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1502 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1503 adding 21
1503 adding 21
1504 $ cat > parent/sub2/.hgsub <<EOF
1504 $ cat > parent/sub2/.hgsub <<EOF
1505 > sub21 = sub21
1505 > sub21 = sub21
1506 > EOF
1506 > EOF
1507 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1507 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1508 adding .hgsub
1508 adding .hgsub
1509 $ hg init parent/sub3
1509 $ hg init parent/sub3
1510 $ echo 3 > parent/sub3/3
1510 $ echo 3 > parent/sub3/3
1511 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1511 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1512 adding 3
1512 adding 3
1513 $ cat > parent/.hgsub <<EOF
1513 $ cat > parent/.hgsub <<EOF
1514 > sub1 = sub1
1514 > sub1 = sub1
1515 > sub2 = sub2
1515 > sub2 = sub2
1516 > sub3 = sub3
1516 > sub3 = sub3
1517 > EOF
1517 > EOF
1518 $ hg -R parent commit -Am '#0 at parent'
1518 $ hg -R parent commit -Am '#0 at parent'
1519 adding .hgsub
1519 adding .hgsub
1520 $ echo '[extensions]' >> parent/.hg/hgrc
1520 $ echo '[extensions]' >> parent/.hg/hgrc
1521 $ echo '# enable extension locally' >> parent/.hg/hgrc
1521 $ echo '# enable extension locally' >> parent/.hg/hgrc
1522 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1522 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1523 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1523 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1524 $ hg -R parent status -S -A
1524 $ hg -R parent status -S -A
1525 reposetup() for $TESTTMP/reposetup-test/parent (glob)
1525 reposetup() for $TESTTMP/reposetup-test/parent (glob)
1526 reposetup() for $TESTTMP/reposetup-test/parent/sub2 (glob)
1526 reposetup() for $TESTTMP/reposetup-test/parent/sub2 (glob)
1527 C .hgsub
1527 C .hgsub
1528 C .hgsubstate
1528 C .hgsubstate
1529 C sub1/1
1529 C sub1/1
1530 C sub2/.hgsub
1530 C sub2/.hgsub
1531 C sub2/.hgsubstate
1531 C sub2/.hgsubstate
1532 C sub2/sub21/21
1532 C sub2/sub21/21
1533 C sub3/3
1533 C sub3/3
1534
1534
1535 $ cd ..
1535 $ cd ..
1536
1536
1537 Prohibit registration of commands that don't use @command (issue5137)
1537 Prohibit registration of commands that don't use @command (issue5137)
1538
1538
1539 $ hg init deprecated
1539 $ hg init deprecated
1540 $ cd deprecated
1540 $ cd deprecated
1541
1541
1542 $ cat <<EOF > deprecatedcmd.py
1542 $ cat <<EOF > deprecatedcmd.py
1543 > def deprecatedcmd(repo, ui):
1543 > def deprecatedcmd(repo, ui):
1544 > pass
1544 > pass
1545 > cmdtable = {
1545 > cmdtable = {
1546 > 'deprecatedcmd': (deprecatedcmd, [], ''),
1546 > 'deprecatedcmd': (deprecatedcmd, [], ''),
1547 > }
1547 > }
1548 > EOF
1548 > EOF
1549 $ cat <<EOF > .hg/hgrc
1549 $ cat <<EOF > .hg/hgrc
1550 > [extensions]
1550 > [extensions]
1551 > deprecatedcmd = `pwd`/deprecatedcmd.py
1551 > deprecatedcmd = `pwd`/deprecatedcmd.py
1552 > mq = !
1552 > mq = !
1553 > hgext.mq = !
1553 > hgext.mq = !
1554 > hgext/mq = !
1554 > hgext/mq = !
1555 > EOF
1555 > EOF
1556
1556
1557 $ hg deprecatedcmd > /dev/null
1557 $ hg deprecatedcmd > /dev/null
1558 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1558 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1559 *** (use @command decorator to register 'deprecatedcmd')
1559 *** (use @command decorator to register 'deprecatedcmd')
1560 hg: unknown command 'deprecatedcmd'
1560 hg: unknown command 'deprecatedcmd'
1561 [255]
1561 [255]
1562
1562
1563 the extension shouldn't be loaded at all so the mq works:
1563 the extension shouldn't be loaded at all so the mq works:
1564
1564
1565 $ hg qseries --config extensions.mq= > /dev/null
1565 $ hg qseries --config extensions.mq= > /dev/null
1566 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1566 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1567 *** (use @command decorator to register 'deprecatedcmd')
1567 *** (use @command decorator to register 'deprecatedcmd')
1568
1568
1569 $ cd ..
1569 $ cd ..
1570
1570
1571 Test synopsis and docstring extending
1571 Test synopsis and docstring extending
1572
1572
1573 $ hg init exthelp
1573 $ hg init exthelp
1574 $ cat > exthelp.py <<EOF
1574 $ cat > exthelp.py <<EOF
1575 > from mercurial import commands, extensions
1575 > from mercurial import commands, extensions
1576 > def exbookmarks(orig, *args, **opts):
1576 > def exbookmarks(orig, *args, **opts):
1577 > return orig(*args, **opts)
1577 > return orig(*args, **opts)
1578 > def uisetup(ui):
1578 > def uisetup(ui):
1579 > synopsis = ' GREPME [--foo] [-x]'
1579 > synopsis = ' GREPME [--foo] [-x]'
1580 > docstring = '''
1580 > docstring = '''
1581 > GREPME make sure that this is in the help!
1581 > GREPME make sure that this is in the help!
1582 > '''
1582 > '''
1583 > extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
1583 > extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
1584 > synopsis, docstring)
1584 > synopsis, docstring)
1585 > EOF
1585 > EOF
1586 $ abspath=`pwd`/exthelp.py
1586 $ abspath=`pwd`/exthelp.py
1587 $ echo '[extensions]' >> $HGRCPATH
1587 $ echo '[extensions]' >> $HGRCPATH
1588 $ echo "exthelp = $abspath" >> $HGRCPATH
1588 $ echo "exthelp = $abspath" >> $HGRCPATH
1589 $ cd exthelp
1589 $ cd exthelp
1590 $ hg help bookmarks | grep GREPME
1590 $ hg help bookmarks | grep GREPME
1591 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1591 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1592 GREPME make sure that this is in the help!
1592 GREPME make sure that this is in the help!
1593 $ cd ..
1593
1594
1595 Show deprecation warning for the use of cmdutil.command
1596
1597 $ cat > nonregistrar.py <<EOF
1598 > from mercurial import cmdutil
1599 > cmdtable = {}
1600 > command = cmdutil.command(cmdtable)
1601 > @command('foo', [], norepo=True)
1602 > def foo(ui):
1603 > pass
1604 > EOF
1605
1606 $ hg --config extensions.nonregistrar=`pwd`/nonregistrar.py version > /dev/null
1607 devel-warn: cmdutil.command is deprecated, use registrar.command to register 'foo'
1608 (compatibility will be dropped after Mercurial-4.6, update your code.) * (glob)
General Comments 0
You need to be logged in to leave comments. Login now