##// END OF EJS Templates
import: pass 'editform' argument to 'cmdutil.getcommiteditor'...
FUJIWARA Katsunori -
r22011:97acb450 default
parent child Browse files
Show More
@@ -1,2636 +1,2637 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 node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
113 editform='', **opts):
113 editform='', **opts):
114 """get appropriate commit message editor according to '--edit' option
114 """get appropriate commit message editor according to '--edit' option
115
115
116 'finishdesc' is a function to be called with edited commit message
116 'finishdesc' is a function to be called with edited commit message
117 (= 'description' of the new changeset) just after editing, but
117 (= 'description' of the new changeset) just after editing, but
118 before checking empty-ness. It should return actual text to be
118 before checking empty-ness. It should return actual text to be
119 stored into history. This allows to change description before
119 stored into history. This allows to change description before
120 storing.
120 storing.
121
121
122 'extramsg' is a extra message to be shown in the editor instead of
122 'extramsg' is a extra message to be shown in the editor instead of
123 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
123 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
124 is automatically added.
124 is automatically added.
125
125
126 'editform' is a dot-separated list of names, to distinguish
126 'editform' is a dot-separated list of names, to distinguish
127 the purpose of commit text editing.
127 the purpose of commit text editing.
128
128
129 'getcommiteditor' returns 'commitforceeditor' regardless of
129 'getcommiteditor' returns 'commitforceeditor' regardless of
130 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
130 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
131 they are specific for usage in MQ.
131 they are specific for usage in MQ.
132 """
132 """
133 if edit or finishdesc or extramsg:
133 if edit or finishdesc or extramsg:
134 return lambda r, c, s: commitforceeditor(r, c, s,
134 return lambda r, c, s: commitforceeditor(r, c, s,
135 finishdesc=finishdesc,
135 finishdesc=finishdesc,
136 extramsg=extramsg,
136 extramsg=extramsg,
137 editform=editform)
137 editform=editform)
138 elif editform:
138 elif editform:
139 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
139 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
140 else:
140 else:
141 return commiteditor
141 return commiteditor
142
142
143 def loglimit(opts):
143 def loglimit(opts):
144 """get the log limit according to option -l/--limit"""
144 """get the log limit according to option -l/--limit"""
145 limit = opts.get('limit')
145 limit = opts.get('limit')
146 if limit:
146 if limit:
147 try:
147 try:
148 limit = int(limit)
148 limit = int(limit)
149 except ValueError:
149 except ValueError:
150 raise util.Abort(_('limit must be a positive integer'))
150 raise util.Abort(_('limit must be a positive integer'))
151 if limit <= 0:
151 if limit <= 0:
152 raise util.Abort(_('limit must be positive'))
152 raise util.Abort(_('limit must be positive'))
153 else:
153 else:
154 limit = None
154 limit = None
155 return limit
155 return limit
156
156
157 def makefilename(repo, pat, node, desc=None,
157 def makefilename(repo, pat, node, desc=None,
158 total=None, seqno=None, revwidth=None, pathname=None):
158 total=None, seqno=None, revwidth=None, pathname=None):
159 node_expander = {
159 node_expander = {
160 'H': lambda: hex(node),
160 'H': lambda: hex(node),
161 'R': lambda: str(repo.changelog.rev(node)),
161 'R': lambda: str(repo.changelog.rev(node)),
162 'h': lambda: short(node),
162 'h': lambda: short(node),
163 'm': lambda: re.sub('[^\w]', '_', str(desc))
163 'm': lambda: re.sub('[^\w]', '_', str(desc))
164 }
164 }
165 expander = {
165 expander = {
166 '%': lambda: '%',
166 '%': lambda: '%',
167 'b': lambda: os.path.basename(repo.root),
167 'b': lambda: os.path.basename(repo.root),
168 }
168 }
169
169
170 try:
170 try:
171 if node:
171 if node:
172 expander.update(node_expander)
172 expander.update(node_expander)
173 if node:
173 if node:
174 expander['r'] = (lambda:
174 expander['r'] = (lambda:
175 str(repo.changelog.rev(node)).zfill(revwidth or 0))
175 str(repo.changelog.rev(node)).zfill(revwidth or 0))
176 if total is not None:
176 if total is not None:
177 expander['N'] = lambda: str(total)
177 expander['N'] = lambda: str(total)
178 if seqno is not None:
178 if seqno is not None:
179 expander['n'] = lambda: str(seqno)
179 expander['n'] = lambda: str(seqno)
180 if total is not None and seqno is not None:
180 if total is not None and seqno is not None:
181 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
181 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
182 if pathname is not None:
182 if pathname is not None:
183 expander['s'] = lambda: os.path.basename(pathname)
183 expander['s'] = lambda: os.path.basename(pathname)
184 expander['d'] = lambda: os.path.dirname(pathname) or '.'
184 expander['d'] = lambda: os.path.dirname(pathname) or '.'
185 expander['p'] = lambda: pathname
185 expander['p'] = lambda: pathname
186
186
187 newname = []
187 newname = []
188 patlen = len(pat)
188 patlen = len(pat)
189 i = 0
189 i = 0
190 while i < patlen:
190 while i < patlen:
191 c = pat[i]
191 c = pat[i]
192 if c == '%':
192 if c == '%':
193 i += 1
193 i += 1
194 c = pat[i]
194 c = pat[i]
195 c = expander[c]()
195 c = expander[c]()
196 newname.append(c)
196 newname.append(c)
197 i += 1
197 i += 1
198 return ''.join(newname)
198 return ''.join(newname)
199 except KeyError, inst:
199 except KeyError, inst:
200 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
200 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
201 inst.args[0])
201 inst.args[0])
202
202
203 def makefileobj(repo, pat, node=None, desc=None, total=None,
203 def makefileobj(repo, pat, node=None, desc=None, total=None,
204 seqno=None, revwidth=None, mode='wb', modemap=None,
204 seqno=None, revwidth=None, mode='wb', modemap=None,
205 pathname=None):
205 pathname=None):
206
206
207 writable = mode not in ('r', 'rb')
207 writable = mode not in ('r', 'rb')
208
208
209 if not pat or pat == '-':
209 if not pat or pat == '-':
210 fp = writable and repo.ui.fout or repo.ui.fin
210 fp = writable and repo.ui.fout or repo.ui.fin
211 if util.safehasattr(fp, 'fileno'):
211 if util.safehasattr(fp, 'fileno'):
212 return os.fdopen(os.dup(fp.fileno()), mode)
212 return os.fdopen(os.dup(fp.fileno()), mode)
213 else:
213 else:
214 # if this fp can't be duped properly, return
214 # if this fp can't be duped properly, return
215 # a dummy object that can be closed
215 # a dummy object that can be closed
216 class wrappedfileobj(object):
216 class wrappedfileobj(object):
217 noop = lambda x: None
217 noop = lambda x: None
218 def __init__(self, f):
218 def __init__(self, f):
219 self.f = f
219 self.f = f
220 def __getattr__(self, attr):
220 def __getattr__(self, attr):
221 if attr == 'close':
221 if attr == 'close':
222 return self.noop
222 return self.noop
223 else:
223 else:
224 return getattr(self.f, attr)
224 return getattr(self.f, attr)
225
225
226 return wrappedfileobj(fp)
226 return wrappedfileobj(fp)
227 if util.safehasattr(pat, 'write') and writable:
227 if util.safehasattr(pat, 'write') and writable:
228 return pat
228 return pat
229 if util.safehasattr(pat, 'read') and 'r' in mode:
229 if util.safehasattr(pat, 'read') and 'r' in mode:
230 return pat
230 return pat
231 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
231 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
232 if modemap is not None:
232 if modemap is not None:
233 mode = modemap.get(fn, mode)
233 mode = modemap.get(fn, mode)
234 if mode == 'wb':
234 if mode == 'wb':
235 modemap[fn] = 'ab'
235 modemap[fn] = 'ab'
236 return open(fn, mode)
236 return open(fn, mode)
237
237
238 def openrevlog(repo, cmd, file_, opts):
238 def openrevlog(repo, cmd, file_, opts):
239 """opens the changelog, manifest, a filelog or a given revlog"""
239 """opens the changelog, manifest, a filelog or a given revlog"""
240 cl = opts['changelog']
240 cl = opts['changelog']
241 mf = opts['manifest']
241 mf = opts['manifest']
242 msg = None
242 msg = None
243 if cl and mf:
243 if cl and mf:
244 msg = _('cannot specify --changelog and --manifest at the same time')
244 msg = _('cannot specify --changelog and --manifest at the same time')
245 elif cl or mf:
245 elif cl or mf:
246 if file_:
246 if file_:
247 msg = _('cannot specify filename with --changelog or --manifest')
247 msg = _('cannot specify filename with --changelog or --manifest')
248 elif not repo:
248 elif not repo:
249 msg = _('cannot specify --changelog or --manifest '
249 msg = _('cannot specify --changelog or --manifest '
250 'without a repository')
250 'without a repository')
251 if msg:
251 if msg:
252 raise util.Abort(msg)
252 raise util.Abort(msg)
253
253
254 r = None
254 r = None
255 if repo:
255 if repo:
256 if cl:
256 if cl:
257 r = repo.unfiltered().changelog
257 r = repo.unfiltered().changelog
258 elif mf:
258 elif mf:
259 r = repo.manifest
259 r = repo.manifest
260 elif file_:
260 elif file_:
261 filelog = repo.file(file_)
261 filelog = repo.file(file_)
262 if len(filelog):
262 if len(filelog):
263 r = filelog
263 r = filelog
264 if not r:
264 if not r:
265 if not file_:
265 if not file_:
266 raise error.CommandError(cmd, _('invalid arguments'))
266 raise error.CommandError(cmd, _('invalid arguments'))
267 if not os.path.isfile(file_):
267 if not os.path.isfile(file_):
268 raise util.Abort(_("revlog '%s' not found") % file_)
268 raise util.Abort(_("revlog '%s' not found") % file_)
269 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
269 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
270 file_[:-2] + ".i")
270 file_[:-2] + ".i")
271 return r
271 return r
272
272
273 def copy(ui, repo, pats, opts, rename=False):
273 def copy(ui, repo, pats, opts, rename=False):
274 # called with the repo lock held
274 # called with the repo lock held
275 #
275 #
276 # hgsep => pathname that uses "/" to separate directories
276 # hgsep => pathname that uses "/" to separate directories
277 # ossep => pathname that uses os.sep to separate directories
277 # ossep => pathname that uses os.sep to separate directories
278 cwd = repo.getcwd()
278 cwd = repo.getcwd()
279 targets = {}
279 targets = {}
280 after = opts.get("after")
280 after = opts.get("after")
281 dryrun = opts.get("dry_run")
281 dryrun = opts.get("dry_run")
282 wctx = repo[None]
282 wctx = repo[None]
283
283
284 def walkpat(pat):
284 def walkpat(pat):
285 srcs = []
285 srcs = []
286 badstates = after and '?' or '?r'
286 badstates = after and '?' or '?r'
287 m = scmutil.match(repo[None], [pat], opts, globbed=True)
287 m = scmutil.match(repo[None], [pat], opts, globbed=True)
288 for abs in repo.walk(m):
288 for abs in repo.walk(m):
289 state = repo.dirstate[abs]
289 state = repo.dirstate[abs]
290 rel = m.rel(abs)
290 rel = m.rel(abs)
291 exact = m.exact(abs)
291 exact = m.exact(abs)
292 if state in badstates:
292 if state in badstates:
293 if exact and state == '?':
293 if exact and state == '?':
294 ui.warn(_('%s: not copying - file is not managed\n') % rel)
294 ui.warn(_('%s: not copying - file is not managed\n') % rel)
295 if exact and state == 'r':
295 if exact and state == 'r':
296 ui.warn(_('%s: not copying - file has been marked for'
296 ui.warn(_('%s: not copying - file has been marked for'
297 ' remove\n') % rel)
297 ' remove\n') % rel)
298 continue
298 continue
299 # abs: hgsep
299 # abs: hgsep
300 # rel: ossep
300 # rel: ossep
301 srcs.append((abs, rel, exact))
301 srcs.append((abs, rel, exact))
302 return srcs
302 return srcs
303
303
304 # abssrc: hgsep
304 # abssrc: hgsep
305 # relsrc: ossep
305 # relsrc: ossep
306 # otarget: ossep
306 # otarget: ossep
307 def copyfile(abssrc, relsrc, otarget, exact):
307 def copyfile(abssrc, relsrc, otarget, exact):
308 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
308 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
309 if '/' in abstarget:
309 if '/' in abstarget:
310 # We cannot normalize abstarget itself, this would prevent
310 # We cannot normalize abstarget itself, this would prevent
311 # case only renames, like a => A.
311 # case only renames, like a => A.
312 abspath, absname = abstarget.rsplit('/', 1)
312 abspath, absname = abstarget.rsplit('/', 1)
313 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
313 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
314 reltarget = repo.pathto(abstarget, cwd)
314 reltarget = repo.pathto(abstarget, cwd)
315 target = repo.wjoin(abstarget)
315 target = repo.wjoin(abstarget)
316 src = repo.wjoin(abssrc)
316 src = repo.wjoin(abssrc)
317 state = repo.dirstate[abstarget]
317 state = repo.dirstate[abstarget]
318
318
319 scmutil.checkportable(ui, abstarget)
319 scmutil.checkportable(ui, abstarget)
320
320
321 # check for collisions
321 # check for collisions
322 prevsrc = targets.get(abstarget)
322 prevsrc = targets.get(abstarget)
323 if prevsrc is not None:
323 if prevsrc is not None:
324 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
324 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
325 (reltarget, repo.pathto(abssrc, cwd),
325 (reltarget, repo.pathto(abssrc, cwd),
326 repo.pathto(prevsrc, cwd)))
326 repo.pathto(prevsrc, cwd)))
327 return
327 return
328
328
329 # check for overwrites
329 # check for overwrites
330 exists = os.path.lexists(target)
330 exists = os.path.lexists(target)
331 samefile = False
331 samefile = False
332 if exists and abssrc != abstarget:
332 if exists and abssrc != abstarget:
333 if (repo.dirstate.normalize(abssrc) ==
333 if (repo.dirstate.normalize(abssrc) ==
334 repo.dirstate.normalize(abstarget)):
334 repo.dirstate.normalize(abstarget)):
335 if not rename:
335 if not rename:
336 ui.warn(_("%s: can't copy - same file\n") % reltarget)
336 ui.warn(_("%s: can't copy - same file\n") % reltarget)
337 return
337 return
338 exists = False
338 exists = False
339 samefile = True
339 samefile = True
340
340
341 if not after and exists or after and state in 'mn':
341 if not after and exists or after and state in 'mn':
342 if not opts['force']:
342 if not opts['force']:
343 ui.warn(_('%s: not overwriting - file exists\n') %
343 ui.warn(_('%s: not overwriting - file exists\n') %
344 reltarget)
344 reltarget)
345 return
345 return
346
346
347 if after:
347 if after:
348 if not exists:
348 if not exists:
349 if rename:
349 if rename:
350 ui.warn(_('%s: not recording move - %s does not exist\n') %
350 ui.warn(_('%s: not recording move - %s does not exist\n') %
351 (relsrc, reltarget))
351 (relsrc, reltarget))
352 else:
352 else:
353 ui.warn(_('%s: not recording copy - %s does not exist\n') %
353 ui.warn(_('%s: not recording copy - %s does not exist\n') %
354 (relsrc, reltarget))
354 (relsrc, reltarget))
355 return
355 return
356 elif not dryrun:
356 elif not dryrun:
357 try:
357 try:
358 if exists:
358 if exists:
359 os.unlink(target)
359 os.unlink(target)
360 targetdir = os.path.dirname(target) or '.'
360 targetdir = os.path.dirname(target) or '.'
361 if not os.path.isdir(targetdir):
361 if not os.path.isdir(targetdir):
362 os.makedirs(targetdir)
362 os.makedirs(targetdir)
363 if samefile:
363 if samefile:
364 tmp = target + "~hgrename"
364 tmp = target + "~hgrename"
365 os.rename(src, tmp)
365 os.rename(src, tmp)
366 os.rename(tmp, target)
366 os.rename(tmp, target)
367 else:
367 else:
368 util.copyfile(src, target)
368 util.copyfile(src, target)
369 srcexists = True
369 srcexists = True
370 except IOError, inst:
370 except IOError, inst:
371 if inst.errno == errno.ENOENT:
371 if inst.errno == errno.ENOENT:
372 ui.warn(_('%s: deleted in working copy\n') % relsrc)
372 ui.warn(_('%s: deleted in working copy\n') % relsrc)
373 srcexists = False
373 srcexists = False
374 else:
374 else:
375 ui.warn(_('%s: cannot copy - %s\n') %
375 ui.warn(_('%s: cannot copy - %s\n') %
376 (relsrc, inst.strerror))
376 (relsrc, inst.strerror))
377 return True # report a failure
377 return True # report a failure
378
378
379 if ui.verbose or not exact:
379 if ui.verbose or not exact:
380 if rename:
380 if rename:
381 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
381 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
382 else:
382 else:
383 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
383 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
384
384
385 targets[abstarget] = abssrc
385 targets[abstarget] = abssrc
386
386
387 # fix up dirstate
387 # fix up dirstate
388 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
388 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
389 dryrun=dryrun, cwd=cwd)
389 dryrun=dryrun, cwd=cwd)
390 if rename and not dryrun:
390 if rename and not dryrun:
391 if not after and srcexists and not samefile:
391 if not after and srcexists and not samefile:
392 util.unlinkpath(repo.wjoin(abssrc))
392 util.unlinkpath(repo.wjoin(abssrc))
393 wctx.forget([abssrc])
393 wctx.forget([abssrc])
394
394
395 # pat: ossep
395 # pat: ossep
396 # dest ossep
396 # dest ossep
397 # srcs: list of (hgsep, hgsep, ossep, bool)
397 # srcs: list of (hgsep, hgsep, ossep, bool)
398 # return: function that takes hgsep and returns ossep
398 # return: function that takes hgsep and returns ossep
399 def targetpathfn(pat, dest, srcs):
399 def targetpathfn(pat, dest, srcs):
400 if os.path.isdir(pat):
400 if os.path.isdir(pat):
401 abspfx = pathutil.canonpath(repo.root, cwd, pat)
401 abspfx = pathutil.canonpath(repo.root, cwd, pat)
402 abspfx = util.localpath(abspfx)
402 abspfx = util.localpath(abspfx)
403 if destdirexists:
403 if destdirexists:
404 striplen = len(os.path.split(abspfx)[0])
404 striplen = len(os.path.split(abspfx)[0])
405 else:
405 else:
406 striplen = len(abspfx)
406 striplen = len(abspfx)
407 if striplen:
407 if striplen:
408 striplen += len(os.sep)
408 striplen += len(os.sep)
409 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
409 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
410 elif destdirexists:
410 elif destdirexists:
411 res = lambda p: os.path.join(dest,
411 res = lambda p: os.path.join(dest,
412 os.path.basename(util.localpath(p)))
412 os.path.basename(util.localpath(p)))
413 else:
413 else:
414 res = lambda p: dest
414 res = lambda p: dest
415 return res
415 return res
416
416
417 # pat: ossep
417 # pat: ossep
418 # dest ossep
418 # dest ossep
419 # srcs: list of (hgsep, hgsep, ossep, bool)
419 # srcs: list of (hgsep, hgsep, ossep, bool)
420 # return: function that takes hgsep and returns ossep
420 # return: function that takes hgsep and returns ossep
421 def targetpathafterfn(pat, dest, srcs):
421 def targetpathafterfn(pat, dest, srcs):
422 if matchmod.patkind(pat):
422 if matchmod.patkind(pat):
423 # a mercurial pattern
423 # a mercurial pattern
424 res = lambda p: os.path.join(dest,
424 res = lambda p: os.path.join(dest,
425 os.path.basename(util.localpath(p)))
425 os.path.basename(util.localpath(p)))
426 else:
426 else:
427 abspfx = pathutil.canonpath(repo.root, cwd, pat)
427 abspfx = pathutil.canonpath(repo.root, cwd, pat)
428 if len(abspfx) < len(srcs[0][0]):
428 if len(abspfx) < len(srcs[0][0]):
429 # A directory. Either the target path contains the last
429 # A directory. Either the target path contains the last
430 # component of the source path or it does not.
430 # component of the source path or it does not.
431 def evalpath(striplen):
431 def evalpath(striplen):
432 score = 0
432 score = 0
433 for s in srcs:
433 for s in srcs:
434 t = os.path.join(dest, util.localpath(s[0])[striplen:])
434 t = os.path.join(dest, util.localpath(s[0])[striplen:])
435 if os.path.lexists(t):
435 if os.path.lexists(t):
436 score += 1
436 score += 1
437 return score
437 return score
438
438
439 abspfx = util.localpath(abspfx)
439 abspfx = util.localpath(abspfx)
440 striplen = len(abspfx)
440 striplen = len(abspfx)
441 if striplen:
441 if striplen:
442 striplen += len(os.sep)
442 striplen += len(os.sep)
443 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
443 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
444 score = evalpath(striplen)
444 score = evalpath(striplen)
445 striplen1 = len(os.path.split(abspfx)[0])
445 striplen1 = len(os.path.split(abspfx)[0])
446 if striplen1:
446 if striplen1:
447 striplen1 += len(os.sep)
447 striplen1 += len(os.sep)
448 if evalpath(striplen1) > score:
448 if evalpath(striplen1) > score:
449 striplen = striplen1
449 striplen = striplen1
450 res = lambda p: os.path.join(dest,
450 res = lambda p: os.path.join(dest,
451 util.localpath(p)[striplen:])
451 util.localpath(p)[striplen:])
452 else:
452 else:
453 # a file
453 # a file
454 if destdirexists:
454 if destdirexists:
455 res = lambda p: os.path.join(dest,
455 res = lambda p: os.path.join(dest,
456 os.path.basename(util.localpath(p)))
456 os.path.basename(util.localpath(p)))
457 else:
457 else:
458 res = lambda p: dest
458 res = lambda p: dest
459 return res
459 return res
460
460
461
461
462 pats = scmutil.expandpats(pats)
462 pats = scmutil.expandpats(pats)
463 if not pats:
463 if not pats:
464 raise util.Abort(_('no source or destination specified'))
464 raise util.Abort(_('no source or destination specified'))
465 if len(pats) == 1:
465 if len(pats) == 1:
466 raise util.Abort(_('no destination specified'))
466 raise util.Abort(_('no destination specified'))
467 dest = pats.pop()
467 dest = pats.pop()
468 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
468 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
469 if not destdirexists:
469 if not destdirexists:
470 if len(pats) > 1 or matchmod.patkind(pats[0]):
470 if len(pats) > 1 or matchmod.patkind(pats[0]):
471 raise util.Abort(_('with multiple sources, destination must be an '
471 raise util.Abort(_('with multiple sources, destination must be an '
472 'existing directory'))
472 'existing directory'))
473 if util.endswithsep(dest):
473 if util.endswithsep(dest):
474 raise util.Abort(_('destination %s is not a directory') % dest)
474 raise util.Abort(_('destination %s is not a directory') % dest)
475
475
476 tfn = targetpathfn
476 tfn = targetpathfn
477 if after:
477 if after:
478 tfn = targetpathafterfn
478 tfn = targetpathafterfn
479 copylist = []
479 copylist = []
480 for pat in pats:
480 for pat in pats:
481 srcs = walkpat(pat)
481 srcs = walkpat(pat)
482 if not srcs:
482 if not srcs:
483 continue
483 continue
484 copylist.append((tfn(pat, dest, srcs), srcs))
484 copylist.append((tfn(pat, dest, srcs), srcs))
485 if not copylist:
485 if not copylist:
486 raise util.Abort(_('no files to copy'))
486 raise util.Abort(_('no files to copy'))
487
487
488 errors = 0
488 errors = 0
489 for targetpath, srcs in copylist:
489 for targetpath, srcs in copylist:
490 for abssrc, relsrc, exact in srcs:
490 for abssrc, relsrc, exact in srcs:
491 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
491 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
492 errors += 1
492 errors += 1
493
493
494 if errors:
494 if errors:
495 ui.warn(_('(consider using --after)\n'))
495 ui.warn(_('(consider using --after)\n'))
496
496
497 return errors != 0
497 return errors != 0
498
498
499 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
499 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
500 runargs=None, appendpid=False):
500 runargs=None, appendpid=False):
501 '''Run a command as a service.'''
501 '''Run a command as a service.'''
502
502
503 def writepid(pid):
503 def writepid(pid):
504 if opts['pid_file']:
504 if opts['pid_file']:
505 mode = appendpid and 'a' or 'w'
505 mode = appendpid and 'a' or 'w'
506 fp = open(opts['pid_file'], mode)
506 fp = open(opts['pid_file'], mode)
507 fp.write(str(pid) + '\n')
507 fp.write(str(pid) + '\n')
508 fp.close()
508 fp.close()
509
509
510 if opts['daemon'] and not opts['daemon_pipefds']:
510 if opts['daemon'] and not opts['daemon_pipefds']:
511 # Signal child process startup with file removal
511 # Signal child process startup with file removal
512 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
512 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
513 os.close(lockfd)
513 os.close(lockfd)
514 try:
514 try:
515 if not runargs:
515 if not runargs:
516 runargs = util.hgcmd() + sys.argv[1:]
516 runargs = util.hgcmd() + sys.argv[1:]
517 runargs.append('--daemon-pipefds=%s' % lockpath)
517 runargs.append('--daemon-pipefds=%s' % lockpath)
518 # Don't pass --cwd to the child process, because we've already
518 # Don't pass --cwd to the child process, because we've already
519 # changed directory.
519 # changed directory.
520 for i in xrange(1, len(runargs)):
520 for i in xrange(1, len(runargs)):
521 if runargs[i].startswith('--cwd='):
521 if runargs[i].startswith('--cwd='):
522 del runargs[i]
522 del runargs[i]
523 break
523 break
524 elif runargs[i].startswith('--cwd'):
524 elif runargs[i].startswith('--cwd'):
525 del runargs[i:i + 2]
525 del runargs[i:i + 2]
526 break
526 break
527 def condfn():
527 def condfn():
528 return not os.path.exists(lockpath)
528 return not os.path.exists(lockpath)
529 pid = util.rundetached(runargs, condfn)
529 pid = util.rundetached(runargs, condfn)
530 if pid < 0:
530 if pid < 0:
531 raise util.Abort(_('child process failed to start'))
531 raise util.Abort(_('child process failed to start'))
532 writepid(pid)
532 writepid(pid)
533 finally:
533 finally:
534 try:
534 try:
535 os.unlink(lockpath)
535 os.unlink(lockpath)
536 except OSError, e:
536 except OSError, e:
537 if e.errno != errno.ENOENT:
537 if e.errno != errno.ENOENT:
538 raise
538 raise
539 if parentfn:
539 if parentfn:
540 return parentfn(pid)
540 return parentfn(pid)
541 else:
541 else:
542 return
542 return
543
543
544 if initfn:
544 if initfn:
545 initfn()
545 initfn()
546
546
547 if not opts['daemon']:
547 if not opts['daemon']:
548 writepid(os.getpid())
548 writepid(os.getpid())
549
549
550 if opts['daemon_pipefds']:
550 if opts['daemon_pipefds']:
551 lockpath = opts['daemon_pipefds']
551 lockpath = opts['daemon_pipefds']
552 try:
552 try:
553 os.setsid()
553 os.setsid()
554 except AttributeError:
554 except AttributeError:
555 pass
555 pass
556 os.unlink(lockpath)
556 os.unlink(lockpath)
557 util.hidewindow()
557 util.hidewindow()
558 sys.stdout.flush()
558 sys.stdout.flush()
559 sys.stderr.flush()
559 sys.stderr.flush()
560
560
561 nullfd = os.open(os.devnull, os.O_RDWR)
561 nullfd = os.open(os.devnull, os.O_RDWR)
562 logfilefd = nullfd
562 logfilefd = nullfd
563 if logfile:
563 if logfile:
564 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
564 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
565 os.dup2(nullfd, 0)
565 os.dup2(nullfd, 0)
566 os.dup2(logfilefd, 1)
566 os.dup2(logfilefd, 1)
567 os.dup2(logfilefd, 2)
567 os.dup2(logfilefd, 2)
568 if nullfd not in (0, 1, 2):
568 if nullfd not in (0, 1, 2):
569 os.close(nullfd)
569 os.close(nullfd)
570 if logfile and logfilefd not in (0, 1, 2):
570 if logfile and logfilefd not in (0, 1, 2):
571 os.close(logfilefd)
571 os.close(logfilefd)
572
572
573 if runfn:
573 if runfn:
574 return runfn()
574 return runfn()
575
575
576 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
576 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
577 """Utility function used by commands.import to import a single patch
577 """Utility function used by commands.import to import a single patch
578
578
579 This function is explicitly defined here to help the evolve extension to
579 This function is explicitly defined here to help the evolve extension to
580 wrap this part of the import logic.
580 wrap this part of the import logic.
581
581
582 The API is currently a bit ugly because it a simple code translation from
582 The API is currently a bit ugly because it a simple code translation from
583 the import command. Feel free to make it better.
583 the import command. Feel free to make it better.
584
584
585 :hunk: a patch (as a binary string)
585 :hunk: a patch (as a binary string)
586 :parents: nodes that will be parent of the created commit
586 :parents: nodes that will be parent of the created commit
587 :opts: the full dict of option passed to the import command
587 :opts: the full dict of option passed to the import command
588 :msgs: list to save commit message to.
588 :msgs: list to save commit message to.
589 (used in case we need to save it when failing)
589 (used in case we need to save it when failing)
590 :updatefunc: a function that update a repo to a given node
590 :updatefunc: a function that update a repo to a given node
591 updatefunc(<repo>, <node>)
591 updatefunc(<repo>, <node>)
592 """
592 """
593 tmpname, message, user, date, branch, nodeid, p1, p2 = \
593 tmpname, message, user, date, branch, nodeid, p1, p2 = \
594 patch.extract(ui, hunk)
594 patch.extract(ui, hunk)
595
595
596 editor = getcommiteditor(**opts)
596 editor = getcommiteditor(editform='import.normal', **opts)
597 update = not opts.get('bypass')
597 update = not opts.get('bypass')
598 strip = opts["strip"]
598 strip = opts["strip"]
599 sim = float(opts.get('similarity') or 0)
599 sim = float(opts.get('similarity') or 0)
600 if not tmpname:
600 if not tmpname:
601 return (None, None, False)
601 return (None, None, False)
602 msg = _('applied to working directory')
602 msg = _('applied to working directory')
603
603
604 rejects = False
604 rejects = False
605
605
606 try:
606 try:
607 cmdline_message = logmessage(ui, opts)
607 cmdline_message = logmessage(ui, opts)
608 if cmdline_message:
608 if cmdline_message:
609 # pickup the cmdline msg
609 # pickup the cmdline msg
610 message = cmdline_message
610 message = cmdline_message
611 elif message:
611 elif message:
612 # pickup the patch msg
612 # pickup the patch msg
613 message = message.strip()
613 message = message.strip()
614 else:
614 else:
615 # launch the editor
615 # launch the editor
616 message = None
616 message = None
617 ui.debug('message:\n%s\n' % message)
617 ui.debug('message:\n%s\n' % message)
618
618
619 if len(parents) == 1:
619 if len(parents) == 1:
620 parents.append(repo[nullid])
620 parents.append(repo[nullid])
621 if opts.get('exact'):
621 if opts.get('exact'):
622 if not nodeid or not p1:
622 if not nodeid or not p1:
623 raise util.Abort(_('not a Mercurial patch'))
623 raise util.Abort(_('not a Mercurial patch'))
624 p1 = repo[p1]
624 p1 = repo[p1]
625 p2 = repo[p2 or nullid]
625 p2 = repo[p2 or nullid]
626 elif p2:
626 elif p2:
627 try:
627 try:
628 p1 = repo[p1]
628 p1 = repo[p1]
629 p2 = repo[p2]
629 p2 = repo[p2]
630 # Without any options, consider p2 only if the
630 # Without any options, consider p2 only if the
631 # patch is being applied on top of the recorded
631 # patch is being applied on top of the recorded
632 # first parent.
632 # first parent.
633 if p1 != parents[0]:
633 if p1 != parents[0]:
634 p1 = parents[0]
634 p1 = parents[0]
635 p2 = repo[nullid]
635 p2 = repo[nullid]
636 except error.RepoError:
636 except error.RepoError:
637 p1, p2 = parents
637 p1, p2 = parents
638 else:
638 else:
639 p1, p2 = parents
639 p1, p2 = parents
640
640
641 n = None
641 n = None
642 if update:
642 if update:
643 if p1 != parents[0]:
643 if p1 != parents[0]:
644 updatefunc(repo, p1.node())
644 updatefunc(repo, p1.node())
645 if p2 != parents[1]:
645 if p2 != parents[1]:
646 repo.setparents(p1.node(), p2.node())
646 repo.setparents(p1.node(), p2.node())
647
647
648 if opts.get('exact') or opts.get('import_branch'):
648 if opts.get('exact') or opts.get('import_branch'):
649 repo.dirstate.setbranch(branch or 'default')
649 repo.dirstate.setbranch(branch or 'default')
650
650
651 partial = opts.get('partial', False)
651 partial = opts.get('partial', False)
652 files = set()
652 files = set()
653 try:
653 try:
654 patch.patch(ui, repo, tmpname, strip=strip, files=files,
654 patch.patch(ui, repo, tmpname, strip=strip, files=files,
655 eolmode=None, similarity=sim / 100.0)
655 eolmode=None, similarity=sim / 100.0)
656 except patch.PatchError, e:
656 except patch.PatchError, e:
657 if not partial:
657 if not partial:
658 raise util.Abort(str(e))
658 raise util.Abort(str(e))
659 if partial:
659 if partial:
660 rejects = True
660 rejects = True
661
661
662 files = list(files)
662 files = list(files)
663 if opts.get('no_commit'):
663 if opts.get('no_commit'):
664 if message:
664 if message:
665 msgs.append(message)
665 msgs.append(message)
666 else:
666 else:
667 if opts.get('exact') or p2:
667 if opts.get('exact') or p2:
668 # If you got here, you either use --force and know what
668 # If you got here, you either use --force and know what
669 # you are doing or used --exact or a merge patch while
669 # you are doing or used --exact or a merge patch while
670 # being updated to its first parent.
670 # being updated to its first parent.
671 m = None
671 m = None
672 else:
672 else:
673 m = scmutil.matchfiles(repo, files or [])
673 m = scmutil.matchfiles(repo, files or [])
674 n = repo.commit(message, opts.get('user') or user,
674 n = repo.commit(message, opts.get('user') or user,
675 opts.get('date') or date, match=m,
675 opts.get('date') or date, match=m,
676 editor=editor, force=partial)
676 editor=editor, force=partial)
677 else:
677 else:
678 if opts.get('exact') or opts.get('import_branch'):
678 if opts.get('exact') or opts.get('import_branch'):
679 branch = branch or 'default'
679 branch = branch or 'default'
680 else:
680 else:
681 branch = p1.branch()
681 branch = p1.branch()
682 store = patch.filestore()
682 store = patch.filestore()
683 try:
683 try:
684 files = set()
684 files = set()
685 try:
685 try:
686 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
686 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
687 files, eolmode=None)
687 files, eolmode=None)
688 except patch.PatchError, e:
688 except patch.PatchError, e:
689 raise util.Abort(str(e))
689 raise util.Abort(str(e))
690 editor = getcommiteditor(editform='import.bypass')
690 memctx = context.makememctx(repo, (p1.node(), p2.node()),
691 memctx = context.makememctx(repo, (p1.node(), p2.node()),
691 message,
692 message,
692 opts.get('user') or user,
693 opts.get('user') or user,
693 opts.get('date') or date,
694 opts.get('date') or date,
694 branch, files, store,
695 branch, files, store,
695 editor=getcommiteditor())
696 editor=editor)
696 n = memctx.commit()
697 n = memctx.commit()
697 finally:
698 finally:
698 store.close()
699 store.close()
699 if opts.get('exact') and hex(n) != nodeid:
700 if opts.get('exact') and hex(n) != nodeid:
700 raise util.Abort(_('patch is damaged or loses information'))
701 raise util.Abort(_('patch is damaged or loses information'))
701 if n:
702 if n:
702 # i18n: refers to a short changeset id
703 # i18n: refers to a short changeset id
703 msg = _('created %s') % short(n)
704 msg = _('created %s') % short(n)
704 return (msg, n, rejects)
705 return (msg, n, rejects)
705 finally:
706 finally:
706 os.unlink(tmpname)
707 os.unlink(tmpname)
707
708
708 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
709 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
709 opts=None):
710 opts=None):
710 '''export changesets as hg patches.'''
711 '''export changesets as hg patches.'''
711
712
712 total = len(revs)
713 total = len(revs)
713 revwidth = max([len(str(rev)) for rev in revs])
714 revwidth = max([len(str(rev)) for rev in revs])
714 filemode = {}
715 filemode = {}
715
716
716 def single(rev, seqno, fp):
717 def single(rev, seqno, fp):
717 ctx = repo[rev]
718 ctx = repo[rev]
718 node = ctx.node()
719 node = ctx.node()
719 parents = [p.node() for p in ctx.parents() if p]
720 parents = [p.node() for p in ctx.parents() if p]
720 branch = ctx.branch()
721 branch = ctx.branch()
721 if switch_parent:
722 if switch_parent:
722 parents.reverse()
723 parents.reverse()
723 prev = (parents and parents[0]) or nullid
724 prev = (parents and parents[0]) or nullid
724
725
725 shouldclose = False
726 shouldclose = False
726 if not fp and len(template) > 0:
727 if not fp and len(template) > 0:
727 desc_lines = ctx.description().rstrip().split('\n')
728 desc_lines = ctx.description().rstrip().split('\n')
728 desc = desc_lines[0] #Commit always has a first line.
729 desc = desc_lines[0] #Commit always has a first line.
729 fp = makefileobj(repo, template, node, desc=desc, total=total,
730 fp = makefileobj(repo, template, node, desc=desc, total=total,
730 seqno=seqno, revwidth=revwidth, mode='wb',
731 seqno=seqno, revwidth=revwidth, mode='wb',
731 modemap=filemode)
732 modemap=filemode)
732 if fp != template:
733 if fp != template:
733 shouldclose = True
734 shouldclose = True
734 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
735 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
735 repo.ui.note("%s\n" % fp.name)
736 repo.ui.note("%s\n" % fp.name)
736
737
737 if not fp:
738 if not fp:
738 write = repo.ui.write
739 write = repo.ui.write
739 else:
740 else:
740 def write(s, **kw):
741 def write(s, **kw):
741 fp.write(s)
742 fp.write(s)
742
743
743
744
744 write("# HG changeset patch\n")
745 write("# HG changeset patch\n")
745 write("# User %s\n" % ctx.user())
746 write("# User %s\n" % ctx.user())
746 write("# Date %d %d\n" % ctx.date())
747 write("# Date %d %d\n" % ctx.date())
747 write("# %s\n" % util.datestr(ctx.date()))
748 write("# %s\n" % util.datestr(ctx.date()))
748 if branch and branch != 'default':
749 if branch and branch != 'default':
749 write("# Branch %s\n" % branch)
750 write("# Branch %s\n" % branch)
750 write("# Node ID %s\n" % hex(node))
751 write("# Node ID %s\n" % hex(node))
751 write("# Parent %s\n" % hex(prev))
752 write("# Parent %s\n" % hex(prev))
752 if len(parents) > 1:
753 if len(parents) > 1:
753 write("# Parent %s\n" % hex(parents[1]))
754 write("# Parent %s\n" % hex(parents[1]))
754 write(ctx.description().rstrip())
755 write(ctx.description().rstrip())
755 write("\n\n")
756 write("\n\n")
756
757
757 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
758 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
758 write(chunk, label=label)
759 write(chunk, label=label)
759
760
760 if shouldclose:
761 if shouldclose:
761 fp.close()
762 fp.close()
762
763
763 for seqno, rev in enumerate(revs):
764 for seqno, rev in enumerate(revs):
764 single(rev, seqno + 1, fp)
765 single(rev, seqno + 1, fp)
765
766
766 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
767 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
767 changes=None, stat=False, fp=None, prefix='',
768 changes=None, stat=False, fp=None, prefix='',
768 listsubrepos=False):
769 listsubrepos=False):
769 '''show diff or diffstat.'''
770 '''show diff or diffstat.'''
770 if fp is None:
771 if fp is None:
771 write = ui.write
772 write = ui.write
772 else:
773 else:
773 def write(s, **kw):
774 def write(s, **kw):
774 fp.write(s)
775 fp.write(s)
775
776
776 if stat:
777 if stat:
777 diffopts = diffopts.copy(context=0)
778 diffopts = diffopts.copy(context=0)
778 width = 80
779 width = 80
779 if not ui.plain():
780 if not ui.plain():
780 width = ui.termwidth()
781 width = ui.termwidth()
781 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
782 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
782 prefix=prefix)
783 prefix=prefix)
783 for chunk, label in patch.diffstatui(util.iterlines(chunks),
784 for chunk, label in patch.diffstatui(util.iterlines(chunks),
784 width=width,
785 width=width,
785 git=diffopts.git):
786 git=diffopts.git):
786 write(chunk, label=label)
787 write(chunk, label=label)
787 else:
788 else:
788 for chunk, label in patch.diffui(repo, node1, node2, match,
789 for chunk, label in patch.diffui(repo, node1, node2, match,
789 changes, diffopts, prefix=prefix):
790 changes, diffopts, prefix=prefix):
790 write(chunk, label=label)
791 write(chunk, label=label)
791
792
792 if listsubrepos:
793 if listsubrepos:
793 ctx1 = repo[node1]
794 ctx1 = repo[node1]
794 ctx2 = repo[node2]
795 ctx2 = repo[node2]
795 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
796 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
796 tempnode2 = node2
797 tempnode2 = node2
797 try:
798 try:
798 if node2 is not None:
799 if node2 is not None:
799 tempnode2 = ctx2.substate[subpath][1]
800 tempnode2 = ctx2.substate[subpath][1]
800 except KeyError:
801 except KeyError:
801 # A subrepo that existed in node1 was deleted between node1 and
802 # A subrepo that existed in node1 was deleted between node1 and
802 # node2 (inclusive). Thus, ctx2's substate won't contain that
803 # node2 (inclusive). Thus, ctx2's substate won't contain that
803 # subpath. The best we can do is to ignore it.
804 # subpath. The best we can do is to ignore it.
804 tempnode2 = None
805 tempnode2 = None
805 submatch = matchmod.narrowmatcher(subpath, match)
806 submatch = matchmod.narrowmatcher(subpath, match)
806 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
807 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
807 stat=stat, fp=fp, prefix=prefix)
808 stat=stat, fp=fp, prefix=prefix)
808
809
809 class changeset_printer(object):
810 class changeset_printer(object):
810 '''show changeset information when templating not requested.'''
811 '''show changeset information when templating not requested.'''
811
812
812 def __init__(self, ui, repo, patch, diffopts, buffered):
813 def __init__(self, ui, repo, patch, diffopts, buffered):
813 self.ui = ui
814 self.ui = ui
814 self.repo = repo
815 self.repo = repo
815 self.buffered = buffered
816 self.buffered = buffered
816 self.patch = patch
817 self.patch = patch
817 self.diffopts = diffopts
818 self.diffopts = diffopts
818 self.header = {}
819 self.header = {}
819 self.hunk = {}
820 self.hunk = {}
820 self.lastheader = None
821 self.lastheader = None
821 self.footer = None
822 self.footer = None
822
823
823 def flush(self, rev):
824 def flush(self, rev):
824 if rev in self.header:
825 if rev in self.header:
825 h = self.header[rev]
826 h = self.header[rev]
826 if h != self.lastheader:
827 if h != self.lastheader:
827 self.lastheader = h
828 self.lastheader = h
828 self.ui.write(h)
829 self.ui.write(h)
829 del self.header[rev]
830 del self.header[rev]
830 if rev in self.hunk:
831 if rev in self.hunk:
831 self.ui.write(self.hunk[rev])
832 self.ui.write(self.hunk[rev])
832 del self.hunk[rev]
833 del self.hunk[rev]
833 return 1
834 return 1
834 return 0
835 return 0
835
836
836 def close(self):
837 def close(self):
837 if self.footer:
838 if self.footer:
838 self.ui.write(self.footer)
839 self.ui.write(self.footer)
839
840
840 def show(self, ctx, copies=None, matchfn=None, **props):
841 def show(self, ctx, copies=None, matchfn=None, **props):
841 if self.buffered:
842 if self.buffered:
842 self.ui.pushbuffer()
843 self.ui.pushbuffer()
843 self._show(ctx, copies, matchfn, props)
844 self._show(ctx, copies, matchfn, props)
844 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
845 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
845 else:
846 else:
846 self._show(ctx, copies, matchfn, props)
847 self._show(ctx, copies, matchfn, props)
847
848
848 def _show(self, ctx, copies, matchfn, props):
849 def _show(self, ctx, copies, matchfn, props):
849 '''show a single changeset or file revision'''
850 '''show a single changeset or file revision'''
850 changenode = ctx.node()
851 changenode = ctx.node()
851 rev = ctx.rev()
852 rev = ctx.rev()
852
853
853 if self.ui.quiet:
854 if self.ui.quiet:
854 self.ui.write("%d:%s\n" % (rev, short(changenode)),
855 self.ui.write("%d:%s\n" % (rev, short(changenode)),
855 label='log.node')
856 label='log.node')
856 return
857 return
857
858
858 log = self.repo.changelog
859 log = self.repo.changelog
859 date = util.datestr(ctx.date())
860 date = util.datestr(ctx.date())
860
861
861 hexfunc = self.ui.debugflag and hex or short
862 hexfunc = self.ui.debugflag and hex or short
862
863
863 parents = [(p, hexfunc(log.node(p)))
864 parents = [(p, hexfunc(log.node(p)))
864 for p in self._meaningful_parentrevs(log, rev)]
865 for p in self._meaningful_parentrevs(log, rev)]
865
866
866 # i18n: column positioning for "hg log"
867 # i18n: column positioning for "hg log"
867 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
868 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
868 label='log.changeset changeset.%s' % ctx.phasestr())
869 label='log.changeset changeset.%s' % ctx.phasestr())
869
870
870 branch = ctx.branch()
871 branch = ctx.branch()
871 # don't show the default branch name
872 # don't show the default branch name
872 if branch != 'default':
873 if branch != 'default':
873 # i18n: column positioning for "hg log"
874 # i18n: column positioning for "hg log"
874 self.ui.write(_("branch: %s\n") % branch,
875 self.ui.write(_("branch: %s\n") % branch,
875 label='log.branch')
876 label='log.branch')
876 for bookmark in self.repo.nodebookmarks(changenode):
877 for bookmark in self.repo.nodebookmarks(changenode):
877 # i18n: column positioning for "hg log"
878 # i18n: column positioning for "hg log"
878 self.ui.write(_("bookmark: %s\n") % bookmark,
879 self.ui.write(_("bookmark: %s\n") % bookmark,
879 label='log.bookmark')
880 label='log.bookmark')
880 for tag in self.repo.nodetags(changenode):
881 for tag in self.repo.nodetags(changenode):
881 # i18n: column positioning for "hg log"
882 # i18n: column positioning for "hg log"
882 self.ui.write(_("tag: %s\n") % tag,
883 self.ui.write(_("tag: %s\n") % tag,
883 label='log.tag')
884 label='log.tag')
884 if self.ui.debugflag and ctx.phase():
885 if self.ui.debugflag and ctx.phase():
885 # i18n: column positioning for "hg log"
886 # i18n: column positioning for "hg log"
886 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
887 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
887 label='log.phase')
888 label='log.phase')
888 for parent in parents:
889 for parent in parents:
889 # i18n: column positioning for "hg log"
890 # i18n: column positioning for "hg log"
890 self.ui.write(_("parent: %d:%s\n") % parent,
891 self.ui.write(_("parent: %d:%s\n") % parent,
891 label='log.parent changeset.%s' % ctx.phasestr())
892 label='log.parent changeset.%s' % ctx.phasestr())
892
893
893 if self.ui.debugflag:
894 if self.ui.debugflag:
894 mnode = ctx.manifestnode()
895 mnode = ctx.manifestnode()
895 # i18n: column positioning for "hg log"
896 # i18n: column positioning for "hg log"
896 self.ui.write(_("manifest: %d:%s\n") %
897 self.ui.write(_("manifest: %d:%s\n") %
897 (self.repo.manifest.rev(mnode), hex(mnode)),
898 (self.repo.manifest.rev(mnode), hex(mnode)),
898 label='ui.debug log.manifest')
899 label='ui.debug log.manifest')
899 # i18n: column positioning for "hg log"
900 # i18n: column positioning for "hg log"
900 self.ui.write(_("user: %s\n") % ctx.user(),
901 self.ui.write(_("user: %s\n") % ctx.user(),
901 label='log.user')
902 label='log.user')
902 # i18n: column positioning for "hg log"
903 # i18n: column positioning for "hg log"
903 self.ui.write(_("date: %s\n") % date,
904 self.ui.write(_("date: %s\n") % date,
904 label='log.date')
905 label='log.date')
905
906
906 if self.ui.debugflag:
907 if self.ui.debugflag:
907 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
908 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
908 for key, value in zip([# i18n: column positioning for "hg log"
909 for key, value in zip([# i18n: column positioning for "hg log"
909 _("files:"),
910 _("files:"),
910 # i18n: column positioning for "hg log"
911 # i18n: column positioning for "hg log"
911 _("files+:"),
912 _("files+:"),
912 # i18n: column positioning for "hg log"
913 # i18n: column positioning for "hg log"
913 _("files-:")], files):
914 _("files-:")], files):
914 if value:
915 if value:
915 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
916 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
916 label='ui.debug log.files')
917 label='ui.debug log.files')
917 elif ctx.files() and self.ui.verbose:
918 elif ctx.files() and self.ui.verbose:
918 # i18n: column positioning for "hg log"
919 # i18n: column positioning for "hg log"
919 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
920 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
920 label='ui.note log.files')
921 label='ui.note log.files')
921 if copies and self.ui.verbose:
922 if copies and self.ui.verbose:
922 copies = ['%s (%s)' % c for c in copies]
923 copies = ['%s (%s)' % c for c in copies]
923 # i18n: column positioning for "hg log"
924 # i18n: column positioning for "hg log"
924 self.ui.write(_("copies: %s\n") % ' '.join(copies),
925 self.ui.write(_("copies: %s\n") % ' '.join(copies),
925 label='ui.note log.copies')
926 label='ui.note log.copies')
926
927
927 extra = ctx.extra()
928 extra = ctx.extra()
928 if extra and self.ui.debugflag:
929 if extra and self.ui.debugflag:
929 for key, value in sorted(extra.items()):
930 for key, value in sorted(extra.items()):
930 # i18n: column positioning for "hg log"
931 # i18n: column positioning for "hg log"
931 self.ui.write(_("extra: %s=%s\n")
932 self.ui.write(_("extra: %s=%s\n")
932 % (key, value.encode('string_escape')),
933 % (key, value.encode('string_escape')),
933 label='ui.debug log.extra')
934 label='ui.debug log.extra')
934
935
935 description = ctx.description().strip()
936 description = ctx.description().strip()
936 if description:
937 if description:
937 if self.ui.verbose:
938 if self.ui.verbose:
938 self.ui.write(_("description:\n"),
939 self.ui.write(_("description:\n"),
939 label='ui.note log.description')
940 label='ui.note log.description')
940 self.ui.write(description,
941 self.ui.write(description,
941 label='ui.note log.description')
942 label='ui.note log.description')
942 self.ui.write("\n\n")
943 self.ui.write("\n\n")
943 else:
944 else:
944 # i18n: column positioning for "hg log"
945 # i18n: column positioning for "hg log"
945 self.ui.write(_("summary: %s\n") %
946 self.ui.write(_("summary: %s\n") %
946 description.splitlines()[0],
947 description.splitlines()[0],
947 label='log.summary')
948 label='log.summary')
948 self.ui.write("\n")
949 self.ui.write("\n")
949
950
950 self.showpatch(changenode, matchfn)
951 self.showpatch(changenode, matchfn)
951
952
952 def showpatch(self, node, matchfn):
953 def showpatch(self, node, matchfn):
953 if not matchfn:
954 if not matchfn:
954 matchfn = self.patch
955 matchfn = self.patch
955 if matchfn:
956 if matchfn:
956 stat = self.diffopts.get('stat')
957 stat = self.diffopts.get('stat')
957 diff = self.diffopts.get('patch')
958 diff = self.diffopts.get('patch')
958 diffopts = patch.diffopts(self.ui, self.diffopts)
959 diffopts = patch.diffopts(self.ui, self.diffopts)
959 prev = self.repo.changelog.parents(node)[0]
960 prev = self.repo.changelog.parents(node)[0]
960 if stat:
961 if stat:
961 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
962 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
962 match=matchfn, stat=True)
963 match=matchfn, stat=True)
963 if diff:
964 if diff:
964 if stat:
965 if stat:
965 self.ui.write("\n")
966 self.ui.write("\n")
966 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
967 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
967 match=matchfn, stat=False)
968 match=matchfn, stat=False)
968 self.ui.write("\n")
969 self.ui.write("\n")
969
970
970 def _meaningful_parentrevs(self, log, rev):
971 def _meaningful_parentrevs(self, log, rev):
971 """Return list of meaningful (or all if debug) parentrevs for rev.
972 """Return list of meaningful (or all if debug) parentrevs for rev.
972
973
973 For merges (two non-nullrev revisions) both parents are meaningful.
974 For merges (two non-nullrev revisions) both parents are meaningful.
974 Otherwise the first parent revision is considered meaningful if it
975 Otherwise the first parent revision is considered meaningful if it
975 is not the preceding revision.
976 is not the preceding revision.
976 """
977 """
977 parents = log.parentrevs(rev)
978 parents = log.parentrevs(rev)
978 if not self.ui.debugflag and parents[1] == nullrev:
979 if not self.ui.debugflag and parents[1] == nullrev:
979 if parents[0] >= rev - 1:
980 if parents[0] >= rev - 1:
980 parents = []
981 parents = []
981 else:
982 else:
982 parents = [parents[0]]
983 parents = [parents[0]]
983 return parents
984 return parents
984
985
985
986
986 class changeset_templater(changeset_printer):
987 class changeset_templater(changeset_printer):
987 '''format changeset information.'''
988 '''format changeset information.'''
988
989
989 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
990 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
990 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
991 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
991 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
992 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
992 defaulttempl = {
993 defaulttempl = {
993 'parent': '{rev}:{node|formatnode} ',
994 'parent': '{rev}:{node|formatnode} ',
994 'manifest': '{rev}:{node|formatnode}',
995 'manifest': '{rev}:{node|formatnode}',
995 'file_copy': '{name} ({source})',
996 'file_copy': '{name} ({source})',
996 'extra': '{key}={value|stringescape}'
997 'extra': '{key}={value|stringescape}'
997 }
998 }
998 # filecopy is preserved for compatibility reasons
999 # filecopy is preserved for compatibility reasons
999 defaulttempl['filecopy'] = defaulttempl['file_copy']
1000 defaulttempl['filecopy'] = defaulttempl['file_copy']
1000 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1001 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1001 cache=defaulttempl)
1002 cache=defaulttempl)
1002 if tmpl:
1003 if tmpl:
1003 self.t.cache['changeset'] = tmpl
1004 self.t.cache['changeset'] = tmpl
1004
1005
1005 self.cache = {}
1006 self.cache = {}
1006
1007
1007 def _meaningful_parentrevs(self, ctx):
1008 def _meaningful_parentrevs(self, ctx):
1008 """Return list of meaningful (or all if debug) parentrevs for rev.
1009 """Return list of meaningful (or all if debug) parentrevs for rev.
1009 """
1010 """
1010 parents = ctx.parents()
1011 parents = ctx.parents()
1011 if len(parents) > 1:
1012 if len(parents) > 1:
1012 return parents
1013 return parents
1013 if self.ui.debugflag:
1014 if self.ui.debugflag:
1014 return [parents[0], self.repo['null']]
1015 return [parents[0], self.repo['null']]
1015 if parents[0].rev() >= ctx.rev() - 1:
1016 if parents[0].rev() >= ctx.rev() - 1:
1016 return []
1017 return []
1017 return parents
1018 return parents
1018
1019
1019 def _show(self, ctx, copies, matchfn, props):
1020 def _show(self, ctx, copies, matchfn, props):
1020 '''show a single changeset or file revision'''
1021 '''show a single changeset or file revision'''
1021
1022
1022 showlist = templatekw.showlist
1023 showlist = templatekw.showlist
1023
1024
1024 # showparents() behaviour depends on ui trace level which
1025 # showparents() behaviour depends on ui trace level which
1025 # causes unexpected behaviours at templating level and makes
1026 # causes unexpected behaviours at templating level and makes
1026 # it harder to extract it in a standalone function. Its
1027 # it harder to extract it in a standalone function. Its
1027 # behaviour cannot be changed so leave it here for now.
1028 # behaviour cannot be changed so leave it here for now.
1028 def showparents(**args):
1029 def showparents(**args):
1029 ctx = args['ctx']
1030 ctx = args['ctx']
1030 parents = [[('rev', p.rev()), ('node', p.hex())]
1031 parents = [[('rev', p.rev()), ('node', p.hex())]
1031 for p in self._meaningful_parentrevs(ctx)]
1032 for p in self._meaningful_parentrevs(ctx)]
1032 return showlist('parent', parents, **args)
1033 return showlist('parent', parents, **args)
1033
1034
1034 props = props.copy()
1035 props = props.copy()
1035 props.update(templatekw.keywords)
1036 props.update(templatekw.keywords)
1036 props['parents'] = showparents
1037 props['parents'] = showparents
1037 props['templ'] = self.t
1038 props['templ'] = self.t
1038 props['ctx'] = ctx
1039 props['ctx'] = ctx
1039 props['repo'] = self.repo
1040 props['repo'] = self.repo
1040 props['revcache'] = {'copies': copies}
1041 props['revcache'] = {'copies': copies}
1041 props['cache'] = self.cache
1042 props['cache'] = self.cache
1042
1043
1043 # find correct templates for current mode
1044 # find correct templates for current mode
1044
1045
1045 tmplmodes = [
1046 tmplmodes = [
1046 (True, None),
1047 (True, None),
1047 (self.ui.verbose, 'verbose'),
1048 (self.ui.verbose, 'verbose'),
1048 (self.ui.quiet, 'quiet'),
1049 (self.ui.quiet, 'quiet'),
1049 (self.ui.debugflag, 'debug'),
1050 (self.ui.debugflag, 'debug'),
1050 ]
1051 ]
1051
1052
1052 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1053 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1053 for mode, postfix in tmplmodes:
1054 for mode, postfix in tmplmodes:
1054 for type in types:
1055 for type in types:
1055 cur = postfix and ('%s_%s' % (type, postfix)) or type
1056 cur = postfix and ('%s_%s' % (type, postfix)) or type
1056 if mode and cur in self.t:
1057 if mode and cur in self.t:
1057 types[type] = cur
1058 types[type] = cur
1058
1059
1059 try:
1060 try:
1060
1061
1061 # write header
1062 # write header
1062 if types['header']:
1063 if types['header']:
1063 h = templater.stringify(self.t(types['header'], **props))
1064 h = templater.stringify(self.t(types['header'], **props))
1064 if self.buffered:
1065 if self.buffered:
1065 self.header[ctx.rev()] = h
1066 self.header[ctx.rev()] = h
1066 else:
1067 else:
1067 if self.lastheader != h:
1068 if self.lastheader != h:
1068 self.lastheader = h
1069 self.lastheader = h
1069 self.ui.write(h)
1070 self.ui.write(h)
1070
1071
1071 # write changeset metadata, then patch if requested
1072 # write changeset metadata, then patch if requested
1072 key = types['changeset']
1073 key = types['changeset']
1073 self.ui.write(templater.stringify(self.t(key, **props)))
1074 self.ui.write(templater.stringify(self.t(key, **props)))
1074 self.showpatch(ctx.node(), matchfn)
1075 self.showpatch(ctx.node(), matchfn)
1075
1076
1076 if types['footer']:
1077 if types['footer']:
1077 if not self.footer:
1078 if not self.footer:
1078 self.footer = templater.stringify(self.t(types['footer'],
1079 self.footer = templater.stringify(self.t(types['footer'],
1079 **props))
1080 **props))
1080
1081
1081 except KeyError, inst:
1082 except KeyError, inst:
1082 msg = _("%s: no key named '%s'")
1083 msg = _("%s: no key named '%s'")
1083 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1084 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1084 except SyntaxError, inst:
1085 except SyntaxError, inst:
1085 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1086 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1086
1087
1087 def gettemplate(ui, tmpl, style):
1088 def gettemplate(ui, tmpl, style):
1088 """
1089 """
1089 Find the template matching the given template spec or style.
1090 Find the template matching the given template spec or style.
1090 """
1091 """
1091
1092
1092 # ui settings
1093 # ui settings
1093 if not tmpl and not style:
1094 if not tmpl and not style:
1094 tmpl = ui.config('ui', 'logtemplate')
1095 tmpl = ui.config('ui', 'logtemplate')
1095 if tmpl:
1096 if tmpl:
1096 try:
1097 try:
1097 tmpl = templater.parsestring(tmpl)
1098 tmpl = templater.parsestring(tmpl)
1098 except SyntaxError:
1099 except SyntaxError:
1099 tmpl = templater.parsestring(tmpl, quoted=False)
1100 tmpl = templater.parsestring(tmpl, quoted=False)
1100 return tmpl, None
1101 return tmpl, None
1101 else:
1102 else:
1102 style = util.expandpath(ui.config('ui', 'style', ''))
1103 style = util.expandpath(ui.config('ui', 'style', ''))
1103
1104
1104 if style:
1105 if style:
1105 mapfile = style
1106 mapfile = style
1106 if not os.path.split(mapfile)[0]:
1107 if not os.path.split(mapfile)[0]:
1107 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1108 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1108 or templater.templatepath(mapfile))
1109 or templater.templatepath(mapfile))
1109 if mapname:
1110 if mapname:
1110 mapfile = mapname
1111 mapfile = mapname
1111 return None, mapfile
1112 return None, mapfile
1112
1113
1113 if not tmpl:
1114 if not tmpl:
1114 return None, None
1115 return None, None
1115
1116
1116 # looks like a literal template?
1117 # looks like a literal template?
1117 if '{' in tmpl:
1118 if '{' in tmpl:
1118 return tmpl, None
1119 return tmpl, None
1119
1120
1120 # perhaps a stock style?
1121 # perhaps a stock style?
1121 if not os.path.split(tmpl)[0]:
1122 if not os.path.split(tmpl)[0]:
1122 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1123 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1123 or templater.templatepath(tmpl))
1124 or templater.templatepath(tmpl))
1124 if mapname and os.path.isfile(mapname):
1125 if mapname and os.path.isfile(mapname):
1125 return None, mapname
1126 return None, mapname
1126
1127
1127 # perhaps it's a reference to [templates]
1128 # perhaps it's a reference to [templates]
1128 t = ui.config('templates', tmpl)
1129 t = ui.config('templates', tmpl)
1129 if t:
1130 if t:
1130 try:
1131 try:
1131 tmpl = templater.parsestring(t)
1132 tmpl = templater.parsestring(t)
1132 except SyntaxError:
1133 except SyntaxError:
1133 tmpl = templater.parsestring(t, quoted=False)
1134 tmpl = templater.parsestring(t, quoted=False)
1134 return tmpl, None
1135 return tmpl, None
1135
1136
1136 if tmpl == 'list':
1137 if tmpl == 'list':
1137 ui.write(_("available styles: %s\n") % templater.stylelist())
1138 ui.write(_("available styles: %s\n") % templater.stylelist())
1138 raise util.Abort(_("specify a template"))
1139 raise util.Abort(_("specify a template"))
1139
1140
1140 # perhaps it's a path to a map or a template
1141 # perhaps it's a path to a map or a template
1141 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1142 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1142 # is it a mapfile for a style?
1143 # is it a mapfile for a style?
1143 if os.path.basename(tmpl).startswith("map-"):
1144 if os.path.basename(tmpl).startswith("map-"):
1144 return None, os.path.realpath(tmpl)
1145 return None, os.path.realpath(tmpl)
1145 tmpl = open(tmpl).read()
1146 tmpl = open(tmpl).read()
1146 return tmpl, None
1147 return tmpl, None
1147
1148
1148 # constant string?
1149 # constant string?
1149 return tmpl, None
1150 return tmpl, None
1150
1151
1151 def show_changeset(ui, repo, opts, buffered=False):
1152 def show_changeset(ui, repo, opts, buffered=False):
1152 """show one changeset using template or regular display.
1153 """show one changeset using template or regular display.
1153
1154
1154 Display format will be the first non-empty hit of:
1155 Display format will be the first non-empty hit of:
1155 1. option 'template'
1156 1. option 'template'
1156 2. option 'style'
1157 2. option 'style'
1157 3. [ui] setting 'logtemplate'
1158 3. [ui] setting 'logtemplate'
1158 4. [ui] setting 'style'
1159 4. [ui] setting 'style'
1159 If all of these values are either the unset or the empty string,
1160 If all of these values are either the unset or the empty string,
1160 regular display via changeset_printer() is done.
1161 regular display via changeset_printer() is done.
1161 """
1162 """
1162 # options
1163 # options
1163 patch = None
1164 patch = None
1164 if opts.get('patch') or opts.get('stat'):
1165 if opts.get('patch') or opts.get('stat'):
1165 patch = scmutil.matchall(repo)
1166 patch = scmutil.matchall(repo)
1166
1167
1167 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1168 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1168
1169
1169 if not tmpl and not mapfile:
1170 if not tmpl and not mapfile:
1170 return changeset_printer(ui, repo, patch, opts, buffered)
1171 return changeset_printer(ui, repo, patch, opts, buffered)
1171
1172
1172 try:
1173 try:
1173 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1174 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1174 except SyntaxError, inst:
1175 except SyntaxError, inst:
1175 raise util.Abort(inst.args[0])
1176 raise util.Abort(inst.args[0])
1176 return t
1177 return t
1177
1178
1178 def showmarker(ui, marker):
1179 def showmarker(ui, marker):
1179 """utility function to display obsolescence marker in a readable way
1180 """utility function to display obsolescence marker in a readable way
1180
1181
1181 To be used by debug function."""
1182 To be used by debug function."""
1182 ui.write(hex(marker.precnode()))
1183 ui.write(hex(marker.precnode()))
1183 for repl in marker.succnodes():
1184 for repl in marker.succnodes():
1184 ui.write(' ')
1185 ui.write(' ')
1185 ui.write(hex(repl))
1186 ui.write(hex(repl))
1186 ui.write(' %X ' % marker._data[2])
1187 ui.write(' %X ' % marker._data[2])
1187 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1188 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1188 sorted(marker.metadata().items()))))
1189 sorted(marker.metadata().items()))))
1189 ui.write('\n')
1190 ui.write('\n')
1190
1191
1191 def finddate(ui, repo, date):
1192 def finddate(ui, repo, date):
1192 """Find the tipmost changeset that matches the given date spec"""
1193 """Find the tipmost changeset that matches the given date spec"""
1193
1194
1194 df = util.matchdate(date)
1195 df = util.matchdate(date)
1195 m = scmutil.matchall(repo)
1196 m = scmutil.matchall(repo)
1196 results = {}
1197 results = {}
1197
1198
1198 def prep(ctx, fns):
1199 def prep(ctx, fns):
1199 d = ctx.date()
1200 d = ctx.date()
1200 if df(d[0]):
1201 if df(d[0]):
1201 results[ctx.rev()] = d
1202 results[ctx.rev()] = d
1202
1203
1203 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1204 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1204 rev = ctx.rev()
1205 rev = ctx.rev()
1205 if rev in results:
1206 if rev in results:
1206 ui.status(_("found revision %s from %s\n") %
1207 ui.status(_("found revision %s from %s\n") %
1207 (rev, util.datestr(results[rev])))
1208 (rev, util.datestr(results[rev])))
1208 return str(rev)
1209 return str(rev)
1209
1210
1210 raise util.Abort(_("revision matching date not found"))
1211 raise util.Abort(_("revision matching date not found"))
1211
1212
1212 def increasingwindows(windowsize=8, sizelimit=512):
1213 def increasingwindows(windowsize=8, sizelimit=512):
1213 while True:
1214 while True:
1214 yield windowsize
1215 yield windowsize
1215 if windowsize < sizelimit:
1216 if windowsize < sizelimit:
1216 windowsize *= 2
1217 windowsize *= 2
1217
1218
1218 class FileWalkError(Exception):
1219 class FileWalkError(Exception):
1219 pass
1220 pass
1220
1221
1221 def walkfilerevs(repo, match, follow, revs, fncache):
1222 def walkfilerevs(repo, match, follow, revs, fncache):
1222 '''Walks the file history for the matched files.
1223 '''Walks the file history for the matched files.
1223
1224
1224 Returns the changeset revs that are involved in the file history.
1225 Returns the changeset revs that are involved in the file history.
1225
1226
1226 Throws FileWalkError if the file history can't be walked using
1227 Throws FileWalkError if the file history can't be walked using
1227 filelogs alone.
1228 filelogs alone.
1228 '''
1229 '''
1229 wanted = set()
1230 wanted = set()
1230 copies = []
1231 copies = []
1231 minrev, maxrev = min(revs), max(revs)
1232 minrev, maxrev = min(revs), max(revs)
1232 def filerevgen(filelog, last):
1233 def filerevgen(filelog, last):
1233 """
1234 """
1234 Only files, no patterns. Check the history of each file.
1235 Only files, no patterns. Check the history of each file.
1235
1236
1236 Examines filelog entries within minrev, maxrev linkrev range
1237 Examines filelog entries within minrev, maxrev linkrev range
1237 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1238 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1238 tuples in backwards order
1239 tuples in backwards order
1239 """
1240 """
1240 cl_count = len(repo)
1241 cl_count = len(repo)
1241 revs = []
1242 revs = []
1242 for j in xrange(0, last + 1):
1243 for j in xrange(0, last + 1):
1243 linkrev = filelog.linkrev(j)
1244 linkrev = filelog.linkrev(j)
1244 if linkrev < minrev:
1245 if linkrev < minrev:
1245 continue
1246 continue
1246 # only yield rev for which we have the changelog, it can
1247 # only yield rev for which we have the changelog, it can
1247 # happen while doing "hg log" during a pull or commit
1248 # happen while doing "hg log" during a pull or commit
1248 if linkrev >= cl_count:
1249 if linkrev >= cl_count:
1249 break
1250 break
1250
1251
1251 parentlinkrevs = []
1252 parentlinkrevs = []
1252 for p in filelog.parentrevs(j):
1253 for p in filelog.parentrevs(j):
1253 if p != nullrev:
1254 if p != nullrev:
1254 parentlinkrevs.append(filelog.linkrev(p))
1255 parentlinkrevs.append(filelog.linkrev(p))
1255 n = filelog.node(j)
1256 n = filelog.node(j)
1256 revs.append((linkrev, parentlinkrevs,
1257 revs.append((linkrev, parentlinkrevs,
1257 follow and filelog.renamed(n)))
1258 follow and filelog.renamed(n)))
1258
1259
1259 return reversed(revs)
1260 return reversed(revs)
1260 def iterfiles():
1261 def iterfiles():
1261 pctx = repo['.']
1262 pctx = repo['.']
1262 for filename in match.files():
1263 for filename in match.files():
1263 if follow:
1264 if follow:
1264 if filename not in pctx:
1265 if filename not in pctx:
1265 raise util.Abort(_('cannot follow file not in parent '
1266 raise util.Abort(_('cannot follow file not in parent '
1266 'revision: "%s"') % filename)
1267 'revision: "%s"') % filename)
1267 yield filename, pctx[filename].filenode()
1268 yield filename, pctx[filename].filenode()
1268 else:
1269 else:
1269 yield filename, None
1270 yield filename, None
1270 for filename_node in copies:
1271 for filename_node in copies:
1271 yield filename_node
1272 yield filename_node
1272
1273
1273 for file_, node in iterfiles():
1274 for file_, node in iterfiles():
1274 filelog = repo.file(file_)
1275 filelog = repo.file(file_)
1275 if not len(filelog):
1276 if not len(filelog):
1276 if node is None:
1277 if node is None:
1277 # A zero count may be a directory or deleted file, so
1278 # A zero count may be a directory or deleted file, so
1278 # try to find matching entries on the slow path.
1279 # try to find matching entries on the slow path.
1279 if follow:
1280 if follow:
1280 raise util.Abort(
1281 raise util.Abort(
1281 _('cannot follow nonexistent file: "%s"') % file_)
1282 _('cannot follow nonexistent file: "%s"') % file_)
1282 raise FileWalkError("Cannot walk via filelog")
1283 raise FileWalkError("Cannot walk via filelog")
1283 else:
1284 else:
1284 continue
1285 continue
1285
1286
1286 if node is None:
1287 if node is None:
1287 last = len(filelog) - 1
1288 last = len(filelog) - 1
1288 else:
1289 else:
1289 last = filelog.rev(node)
1290 last = filelog.rev(node)
1290
1291
1291
1292
1292 # keep track of all ancestors of the file
1293 # keep track of all ancestors of the file
1293 ancestors = set([filelog.linkrev(last)])
1294 ancestors = set([filelog.linkrev(last)])
1294
1295
1295 # iterate from latest to oldest revision
1296 # iterate from latest to oldest revision
1296 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1297 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1297 if not follow:
1298 if not follow:
1298 if rev > maxrev:
1299 if rev > maxrev:
1299 continue
1300 continue
1300 else:
1301 else:
1301 # Note that last might not be the first interesting
1302 # Note that last might not be the first interesting
1302 # rev to us:
1303 # rev to us:
1303 # if the file has been changed after maxrev, we'll
1304 # if the file has been changed after maxrev, we'll
1304 # have linkrev(last) > maxrev, and we still need
1305 # have linkrev(last) > maxrev, and we still need
1305 # to explore the file graph
1306 # to explore the file graph
1306 if rev not in ancestors:
1307 if rev not in ancestors:
1307 continue
1308 continue
1308 # XXX insert 1327 fix here
1309 # XXX insert 1327 fix here
1309 if flparentlinkrevs:
1310 if flparentlinkrevs:
1310 ancestors.update(flparentlinkrevs)
1311 ancestors.update(flparentlinkrevs)
1311
1312
1312 fncache.setdefault(rev, []).append(file_)
1313 fncache.setdefault(rev, []).append(file_)
1313 wanted.add(rev)
1314 wanted.add(rev)
1314 if copied:
1315 if copied:
1315 copies.append(copied)
1316 copies.append(copied)
1316
1317
1317 return wanted
1318 return wanted
1318
1319
1319 def walkchangerevs(repo, match, opts, prepare):
1320 def walkchangerevs(repo, match, opts, prepare):
1320 '''Iterate over files and the revs in which they changed.
1321 '''Iterate over files and the revs in which they changed.
1321
1322
1322 Callers most commonly need to iterate backwards over the history
1323 Callers most commonly need to iterate backwards over the history
1323 in which they are interested. Doing so has awful (quadratic-looking)
1324 in which they are interested. Doing so has awful (quadratic-looking)
1324 performance, so we use iterators in a "windowed" way.
1325 performance, so we use iterators in a "windowed" way.
1325
1326
1326 We walk a window of revisions in the desired order. Within the
1327 We walk a window of revisions in the desired order. Within the
1327 window, we first walk forwards to gather data, then in the desired
1328 window, we first walk forwards to gather data, then in the desired
1328 order (usually backwards) to display it.
1329 order (usually backwards) to display it.
1329
1330
1330 This function returns an iterator yielding contexts. Before
1331 This function returns an iterator yielding contexts. Before
1331 yielding each context, the iterator will first call the prepare
1332 yielding each context, the iterator will first call the prepare
1332 function on each context in the window in forward order.'''
1333 function on each context in the window in forward order.'''
1333
1334
1334 follow = opts.get('follow') or opts.get('follow_first')
1335 follow = opts.get('follow') or opts.get('follow_first')
1335
1336
1336 if opts.get('rev'):
1337 if opts.get('rev'):
1337 revs = scmutil.revrange(repo, opts.get('rev'))
1338 revs = scmutil.revrange(repo, opts.get('rev'))
1338 elif follow:
1339 elif follow:
1339 revs = repo.revs('reverse(:.)')
1340 revs = repo.revs('reverse(:.)')
1340 else:
1341 else:
1341 revs = revset.spanset(repo)
1342 revs = revset.spanset(repo)
1342 revs.reverse()
1343 revs.reverse()
1343 if not revs:
1344 if not revs:
1344 return []
1345 return []
1345 wanted = set()
1346 wanted = set()
1346 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1347 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1347 fncache = {}
1348 fncache = {}
1348 change = repo.changectx
1349 change = repo.changectx
1349
1350
1350 # First step is to fill wanted, the set of revisions that we want to yield.
1351 # First step is to fill wanted, the set of revisions that we want to yield.
1351 # When it does not induce extra cost, we also fill fncache for revisions in
1352 # When it does not induce extra cost, we also fill fncache for revisions in
1352 # wanted: a cache of filenames that were changed (ctx.files()) and that
1353 # wanted: a cache of filenames that were changed (ctx.files()) and that
1353 # match the file filtering conditions.
1354 # match the file filtering conditions.
1354
1355
1355 if not slowpath and not match.files():
1356 if not slowpath and not match.files():
1356 # No files, no patterns. Display all revs.
1357 # No files, no patterns. Display all revs.
1357 wanted = revs
1358 wanted = revs
1358
1359
1359 if not slowpath and match.files():
1360 if not slowpath and match.files():
1360 # We only have to read through the filelog to find wanted revisions
1361 # We only have to read through the filelog to find wanted revisions
1361
1362
1362 try:
1363 try:
1363 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1364 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1364 except FileWalkError:
1365 except FileWalkError:
1365 slowpath = True
1366 slowpath = True
1366
1367
1367 # We decided to fall back to the slowpath because at least one
1368 # We decided to fall back to the slowpath because at least one
1368 # of the paths was not a file. Check to see if at least one of them
1369 # of the paths was not a file. Check to see if at least one of them
1369 # existed in history, otherwise simply return
1370 # existed in history, otherwise simply return
1370 for path in match.files():
1371 for path in match.files():
1371 if path == '.' or path in repo.store:
1372 if path == '.' or path in repo.store:
1372 break
1373 break
1373 else:
1374 else:
1374 return []
1375 return []
1375
1376
1376 if slowpath:
1377 if slowpath:
1377 # We have to read the changelog to match filenames against
1378 # We have to read the changelog to match filenames against
1378 # changed files
1379 # changed files
1379
1380
1380 if follow:
1381 if follow:
1381 raise util.Abort(_('can only follow copies/renames for explicit '
1382 raise util.Abort(_('can only follow copies/renames for explicit '
1382 'filenames'))
1383 'filenames'))
1383
1384
1384 # The slow path checks files modified in every changeset.
1385 # The slow path checks files modified in every changeset.
1385 # This is really slow on large repos, so compute the set lazily.
1386 # This is really slow on large repos, so compute the set lazily.
1386 class lazywantedset(object):
1387 class lazywantedset(object):
1387 def __init__(self):
1388 def __init__(self):
1388 self.set = set()
1389 self.set = set()
1389 self.revs = set(revs)
1390 self.revs = set(revs)
1390
1391
1391 # No need to worry about locality here because it will be accessed
1392 # No need to worry about locality here because it will be accessed
1392 # in the same order as the increasing window below.
1393 # in the same order as the increasing window below.
1393 def __contains__(self, value):
1394 def __contains__(self, value):
1394 if value in self.set:
1395 if value in self.set:
1395 return True
1396 return True
1396 elif not value in self.revs:
1397 elif not value in self.revs:
1397 return False
1398 return False
1398 else:
1399 else:
1399 self.revs.discard(value)
1400 self.revs.discard(value)
1400 ctx = change(value)
1401 ctx = change(value)
1401 matches = filter(match, ctx.files())
1402 matches = filter(match, ctx.files())
1402 if matches:
1403 if matches:
1403 fncache[value] = matches
1404 fncache[value] = matches
1404 self.set.add(value)
1405 self.set.add(value)
1405 return True
1406 return True
1406 return False
1407 return False
1407
1408
1408 def discard(self, value):
1409 def discard(self, value):
1409 self.revs.discard(value)
1410 self.revs.discard(value)
1410 self.set.discard(value)
1411 self.set.discard(value)
1411
1412
1412 wanted = lazywantedset()
1413 wanted = lazywantedset()
1413
1414
1414 class followfilter(object):
1415 class followfilter(object):
1415 def __init__(self, onlyfirst=False):
1416 def __init__(self, onlyfirst=False):
1416 self.startrev = nullrev
1417 self.startrev = nullrev
1417 self.roots = set()
1418 self.roots = set()
1418 self.onlyfirst = onlyfirst
1419 self.onlyfirst = onlyfirst
1419
1420
1420 def match(self, rev):
1421 def match(self, rev):
1421 def realparents(rev):
1422 def realparents(rev):
1422 if self.onlyfirst:
1423 if self.onlyfirst:
1423 return repo.changelog.parentrevs(rev)[0:1]
1424 return repo.changelog.parentrevs(rev)[0:1]
1424 else:
1425 else:
1425 return filter(lambda x: x != nullrev,
1426 return filter(lambda x: x != nullrev,
1426 repo.changelog.parentrevs(rev))
1427 repo.changelog.parentrevs(rev))
1427
1428
1428 if self.startrev == nullrev:
1429 if self.startrev == nullrev:
1429 self.startrev = rev
1430 self.startrev = rev
1430 return True
1431 return True
1431
1432
1432 if rev > self.startrev:
1433 if rev > self.startrev:
1433 # forward: all descendants
1434 # forward: all descendants
1434 if not self.roots:
1435 if not self.roots:
1435 self.roots.add(self.startrev)
1436 self.roots.add(self.startrev)
1436 for parent in realparents(rev):
1437 for parent in realparents(rev):
1437 if parent in self.roots:
1438 if parent in self.roots:
1438 self.roots.add(rev)
1439 self.roots.add(rev)
1439 return True
1440 return True
1440 else:
1441 else:
1441 # backwards: all parents
1442 # backwards: all parents
1442 if not self.roots:
1443 if not self.roots:
1443 self.roots.update(realparents(self.startrev))
1444 self.roots.update(realparents(self.startrev))
1444 if rev in self.roots:
1445 if rev in self.roots:
1445 self.roots.remove(rev)
1446 self.roots.remove(rev)
1446 self.roots.update(realparents(rev))
1447 self.roots.update(realparents(rev))
1447 return True
1448 return True
1448
1449
1449 return False
1450 return False
1450
1451
1451 # it might be worthwhile to do this in the iterator if the rev range
1452 # it might be worthwhile to do this in the iterator if the rev range
1452 # is descending and the prune args are all within that range
1453 # is descending and the prune args are all within that range
1453 for rev in opts.get('prune', ()):
1454 for rev in opts.get('prune', ()):
1454 rev = repo[rev].rev()
1455 rev = repo[rev].rev()
1455 ff = followfilter()
1456 ff = followfilter()
1456 stop = min(revs[0], revs[-1])
1457 stop = min(revs[0], revs[-1])
1457 for x in xrange(rev, stop - 1, -1):
1458 for x in xrange(rev, stop - 1, -1):
1458 if ff.match(x):
1459 if ff.match(x):
1459 wanted = wanted - [x]
1460 wanted = wanted - [x]
1460
1461
1461 # Now that wanted is correctly initialized, we can iterate over the
1462 # Now that wanted is correctly initialized, we can iterate over the
1462 # revision range, yielding only revisions in wanted.
1463 # revision range, yielding only revisions in wanted.
1463 def iterate():
1464 def iterate():
1464 if follow and not match.files():
1465 if follow and not match.files():
1465 ff = followfilter(onlyfirst=opts.get('follow_first'))
1466 ff = followfilter(onlyfirst=opts.get('follow_first'))
1466 def want(rev):
1467 def want(rev):
1467 return ff.match(rev) and rev in wanted
1468 return ff.match(rev) and rev in wanted
1468 else:
1469 else:
1469 def want(rev):
1470 def want(rev):
1470 return rev in wanted
1471 return rev in wanted
1471
1472
1472 it = iter(revs)
1473 it = iter(revs)
1473 stopiteration = False
1474 stopiteration = False
1474 for windowsize in increasingwindows():
1475 for windowsize in increasingwindows():
1475 nrevs = []
1476 nrevs = []
1476 for i in xrange(windowsize):
1477 for i in xrange(windowsize):
1477 try:
1478 try:
1478 rev = it.next()
1479 rev = it.next()
1479 if want(rev):
1480 if want(rev):
1480 nrevs.append(rev)
1481 nrevs.append(rev)
1481 except (StopIteration):
1482 except (StopIteration):
1482 stopiteration = True
1483 stopiteration = True
1483 break
1484 break
1484 for rev in sorted(nrevs):
1485 for rev in sorted(nrevs):
1485 fns = fncache.get(rev)
1486 fns = fncache.get(rev)
1486 ctx = change(rev)
1487 ctx = change(rev)
1487 if not fns:
1488 if not fns:
1488 def fns_generator():
1489 def fns_generator():
1489 for f in ctx.files():
1490 for f in ctx.files():
1490 if match(f):
1491 if match(f):
1491 yield f
1492 yield f
1492 fns = fns_generator()
1493 fns = fns_generator()
1493 prepare(ctx, fns)
1494 prepare(ctx, fns)
1494 for rev in nrevs:
1495 for rev in nrevs:
1495 yield change(rev)
1496 yield change(rev)
1496
1497
1497 if stopiteration:
1498 if stopiteration:
1498 break
1499 break
1499
1500
1500 return iterate()
1501 return iterate()
1501
1502
1502 def _makelogfilematcher(repo, files, followfirst):
1503 def _makelogfilematcher(repo, files, followfirst):
1503 # When displaying a revision with --patch --follow FILE, we have
1504 # When displaying a revision with --patch --follow FILE, we have
1504 # to know which file of the revision must be diffed. With
1505 # to know which file of the revision must be diffed. With
1505 # --follow, we want the names of the ancestors of FILE in the
1506 # --follow, we want the names of the ancestors of FILE in the
1506 # revision, stored in "fcache". "fcache" is populated by
1507 # revision, stored in "fcache". "fcache" is populated by
1507 # reproducing the graph traversal already done by --follow revset
1508 # reproducing the graph traversal already done by --follow revset
1508 # and relating linkrevs to file names (which is not "correct" but
1509 # and relating linkrevs to file names (which is not "correct" but
1509 # good enough).
1510 # good enough).
1510 fcache = {}
1511 fcache = {}
1511 fcacheready = [False]
1512 fcacheready = [False]
1512 pctx = repo['.']
1513 pctx = repo['.']
1513
1514
1514 def populate():
1515 def populate():
1515 for fn in files:
1516 for fn in files:
1516 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1517 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1517 for c in i:
1518 for c in i:
1518 fcache.setdefault(c.linkrev(), set()).add(c.path())
1519 fcache.setdefault(c.linkrev(), set()).add(c.path())
1519
1520
1520 def filematcher(rev):
1521 def filematcher(rev):
1521 if not fcacheready[0]:
1522 if not fcacheready[0]:
1522 # Lazy initialization
1523 # Lazy initialization
1523 fcacheready[0] = True
1524 fcacheready[0] = True
1524 populate()
1525 populate()
1525 return scmutil.matchfiles(repo, fcache.get(rev, []))
1526 return scmutil.matchfiles(repo, fcache.get(rev, []))
1526
1527
1527 return filematcher
1528 return filematcher
1528
1529
1529 def _makelogrevset(repo, pats, opts, revs):
1530 def _makelogrevset(repo, pats, opts, revs):
1530 """Return (expr, filematcher) where expr is a revset string built
1531 """Return (expr, filematcher) where expr is a revset string built
1531 from log options and file patterns or None. If --stat or --patch
1532 from log options and file patterns or None. If --stat or --patch
1532 are not passed filematcher is None. Otherwise it is a callable
1533 are not passed filematcher is None. Otherwise it is a callable
1533 taking a revision number and returning a match objects filtering
1534 taking a revision number and returning a match objects filtering
1534 the files to be detailed when displaying the revision.
1535 the files to be detailed when displaying the revision.
1535 """
1536 """
1536 opt2revset = {
1537 opt2revset = {
1537 'no_merges': ('not merge()', None),
1538 'no_merges': ('not merge()', None),
1538 'only_merges': ('merge()', None),
1539 'only_merges': ('merge()', None),
1539 '_ancestors': ('ancestors(%(val)s)', None),
1540 '_ancestors': ('ancestors(%(val)s)', None),
1540 '_fancestors': ('_firstancestors(%(val)s)', None),
1541 '_fancestors': ('_firstancestors(%(val)s)', None),
1541 '_descendants': ('descendants(%(val)s)', None),
1542 '_descendants': ('descendants(%(val)s)', None),
1542 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1543 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1543 '_matchfiles': ('_matchfiles(%(val)s)', None),
1544 '_matchfiles': ('_matchfiles(%(val)s)', None),
1544 'date': ('date(%(val)r)', None),
1545 'date': ('date(%(val)r)', None),
1545 'branch': ('branch(%(val)r)', ' or '),
1546 'branch': ('branch(%(val)r)', ' or '),
1546 '_patslog': ('filelog(%(val)r)', ' or '),
1547 '_patslog': ('filelog(%(val)r)', ' or '),
1547 '_patsfollow': ('follow(%(val)r)', ' or '),
1548 '_patsfollow': ('follow(%(val)r)', ' or '),
1548 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1549 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1549 'keyword': ('keyword(%(val)r)', ' or '),
1550 'keyword': ('keyword(%(val)r)', ' or '),
1550 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1551 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1551 'user': ('user(%(val)r)', ' or '),
1552 'user': ('user(%(val)r)', ' or '),
1552 }
1553 }
1553
1554
1554 opts = dict(opts)
1555 opts = dict(opts)
1555 # follow or not follow?
1556 # follow or not follow?
1556 follow = opts.get('follow') or opts.get('follow_first')
1557 follow = opts.get('follow') or opts.get('follow_first')
1557 followfirst = opts.get('follow_first') and 1 or 0
1558 followfirst = opts.get('follow_first') and 1 or 0
1558 # --follow with FILE behaviour depends on revs...
1559 # --follow with FILE behaviour depends on revs...
1559 it = iter(revs)
1560 it = iter(revs)
1560 startrev = it.next()
1561 startrev = it.next()
1561 try:
1562 try:
1562 followdescendants = startrev < it.next()
1563 followdescendants = startrev < it.next()
1563 except (StopIteration):
1564 except (StopIteration):
1564 followdescendants = False
1565 followdescendants = False
1565
1566
1566 # branch and only_branch are really aliases and must be handled at
1567 # branch and only_branch are really aliases and must be handled at
1567 # the same time
1568 # the same time
1568 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1569 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1569 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1570 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1570 # pats/include/exclude are passed to match.match() directly in
1571 # pats/include/exclude are passed to match.match() directly in
1571 # _matchfiles() revset but walkchangerevs() builds its matcher with
1572 # _matchfiles() revset but walkchangerevs() builds its matcher with
1572 # scmutil.match(). The difference is input pats are globbed on
1573 # scmutil.match(). The difference is input pats are globbed on
1573 # platforms without shell expansion (windows).
1574 # platforms without shell expansion (windows).
1574 pctx = repo[None]
1575 pctx = repo[None]
1575 match, pats = scmutil.matchandpats(pctx, pats, opts)
1576 match, pats = scmutil.matchandpats(pctx, pats, opts)
1576 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1577 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1577 if not slowpath:
1578 if not slowpath:
1578 for f in match.files():
1579 for f in match.files():
1579 if follow and f not in pctx:
1580 if follow and f not in pctx:
1580 # If the file exists, it may be a directory, so let it
1581 # If the file exists, it may be a directory, so let it
1581 # take the slow path.
1582 # take the slow path.
1582 if os.path.exists(repo.wjoin(f)):
1583 if os.path.exists(repo.wjoin(f)):
1583 slowpath = True
1584 slowpath = True
1584 continue
1585 continue
1585 else:
1586 else:
1586 raise util.Abort(_('cannot follow file not in parent '
1587 raise util.Abort(_('cannot follow file not in parent '
1587 'revision: "%s"') % f)
1588 'revision: "%s"') % f)
1588 filelog = repo.file(f)
1589 filelog = repo.file(f)
1589 if not filelog:
1590 if not filelog:
1590 # A zero count may be a directory or deleted file, so
1591 # A zero count may be a directory or deleted file, so
1591 # try to find matching entries on the slow path.
1592 # try to find matching entries on the slow path.
1592 if follow:
1593 if follow:
1593 raise util.Abort(
1594 raise util.Abort(
1594 _('cannot follow nonexistent file: "%s"') % f)
1595 _('cannot follow nonexistent file: "%s"') % f)
1595 slowpath = True
1596 slowpath = True
1596
1597
1597 # We decided to fall back to the slowpath because at least one
1598 # We decided to fall back to the slowpath because at least one
1598 # of the paths was not a file. Check to see if at least one of them
1599 # of the paths was not a file. Check to see if at least one of them
1599 # existed in history - in that case, we'll continue down the
1600 # existed in history - in that case, we'll continue down the
1600 # slowpath; otherwise, we can turn off the slowpath
1601 # slowpath; otherwise, we can turn off the slowpath
1601 if slowpath:
1602 if slowpath:
1602 for path in match.files():
1603 for path in match.files():
1603 if path == '.' or path in repo.store:
1604 if path == '.' or path in repo.store:
1604 break
1605 break
1605 else:
1606 else:
1606 slowpath = False
1607 slowpath = False
1607
1608
1608 if slowpath:
1609 if slowpath:
1609 # See walkchangerevs() slow path.
1610 # See walkchangerevs() slow path.
1610 #
1611 #
1611 # pats/include/exclude cannot be represented as separate
1612 # pats/include/exclude cannot be represented as separate
1612 # revset expressions as their filtering logic applies at file
1613 # revset expressions as their filtering logic applies at file
1613 # level. For instance "-I a -X a" matches a revision touching
1614 # level. For instance "-I a -X a" matches a revision touching
1614 # "a" and "b" while "file(a) and not file(b)" does
1615 # "a" and "b" while "file(a) and not file(b)" does
1615 # not. Besides, filesets are evaluated against the working
1616 # not. Besides, filesets are evaluated against the working
1616 # directory.
1617 # directory.
1617 matchargs = ['r:', 'd:relpath']
1618 matchargs = ['r:', 'd:relpath']
1618 for p in pats:
1619 for p in pats:
1619 matchargs.append('p:' + p)
1620 matchargs.append('p:' + p)
1620 for p in opts.get('include', []):
1621 for p in opts.get('include', []):
1621 matchargs.append('i:' + p)
1622 matchargs.append('i:' + p)
1622 for p in opts.get('exclude', []):
1623 for p in opts.get('exclude', []):
1623 matchargs.append('x:' + p)
1624 matchargs.append('x:' + p)
1624 matchargs = ','.join(('%r' % p) for p in matchargs)
1625 matchargs = ','.join(('%r' % p) for p in matchargs)
1625 opts['_matchfiles'] = matchargs
1626 opts['_matchfiles'] = matchargs
1626 else:
1627 else:
1627 if follow:
1628 if follow:
1628 fpats = ('_patsfollow', '_patsfollowfirst')
1629 fpats = ('_patsfollow', '_patsfollowfirst')
1629 fnopats = (('_ancestors', '_fancestors'),
1630 fnopats = (('_ancestors', '_fancestors'),
1630 ('_descendants', '_fdescendants'))
1631 ('_descendants', '_fdescendants'))
1631 if pats:
1632 if pats:
1632 # follow() revset interprets its file argument as a
1633 # follow() revset interprets its file argument as a
1633 # manifest entry, so use match.files(), not pats.
1634 # manifest entry, so use match.files(), not pats.
1634 opts[fpats[followfirst]] = list(match.files())
1635 opts[fpats[followfirst]] = list(match.files())
1635 else:
1636 else:
1636 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1637 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1637 else:
1638 else:
1638 opts['_patslog'] = list(pats)
1639 opts['_patslog'] = list(pats)
1639
1640
1640 filematcher = None
1641 filematcher = None
1641 if opts.get('patch') or opts.get('stat'):
1642 if opts.get('patch') or opts.get('stat'):
1642 # When following files, track renames via a special matcher.
1643 # When following files, track renames via a special matcher.
1643 # If we're forced to take the slowpath it means we're following
1644 # If we're forced to take the slowpath it means we're following
1644 # at least one pattern/directory, so don't bother with rename tracking.
1645 # at least one pattern/directory, so don't bother with rename tracking.
1645 if follow and not match.always() and not slowpath:
1646 if follow and not match.always() and not slowpath:
1646 # _makelogfilematcher expects its files argument to be relative to
1647 # _makelogfilematcher expects its files argument to be relative to
1647 # the repo root, so use match.files(), not pats.
1648 # the repo root, so use match.files(), not pats.
1648 filematcher = _makelogfilematcher(repo, match.files(), followfirst)
1649 filematcher = _makelogfilematcher(repo, match.files(), followfirst)
1649 else:
1650 else:
1650 filematcher = lambda rev: match
1651 filematcher = lambda rev: match
1651
1652
1652 expr = []
1653 expr = []
1653 for op, val in opts.iteritems():
1654 for op, val in opts.iteritems():
1654 if not val:
1655 if not val:
1655 continue
1656 continue
1656 if op not in opt2revset:
1657 if op not in opt2revset:
1657 continue
1658 continue
1658 revop, andor = opt2revset[op]
1659 revop, andor = opt2revset[op]
1659 if '%(val)' not in revop:
1660 if '%(val)' not in revop:
1660 expr.append(revop)
1661 expr.append(revop)
1661 else:
1662 else:
1662 if not isinstance(val, list):
1663 if not isinstance(val, list):
1663 e = revop % {'val': val}
1664 e = revop % {'val': val}
1664 else:
1665 else:
1665 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1666 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1666 expr.append(e)
1667 expr.append(e)
1667
1668
1668 if expr:
1669 if expr:
1669 expr = '(' + ' and '.join(expr) + ')'
1670 expr = '(' + ' and '.join(expr) + ')'
1670 else:
1671 else:
1671 expr = None
1672 expr = None
1672 return expr, filematcher
1673 return expr, filematcher
1673
1674
1674 def getgraphlogrevs(repo, pats, opts):
1675 def getgraphlogrevs(repo, pats, opts):
1675 """Return (revs, expr, filematcher) where revs is an iterable of
1676 """Return (revs, expr, filematcher) where revs is an iterable of
1676 revision numbers, expr is a revset string built from log options
1677 revision numbers, expr is a revset string built from log options
1677 and file patterns or None, and used to filter 'revs'. If --stat or
1678 and file patterns or None, and used to filter 'revs'. If --stat or
1678 --patch are not passed filematcher is None. Otherwise it is a
1679 --patch are not passed filematcher is None. Otherwise it is a
1679 callable taking a revision number and returning a match objects
1680 callable taking a revision number and returning a match objects
1680 filtering the files to be detailed when displaying the revision.
1681 filtering the files to be detailed when displaying the revision.
1681 """
1682 """
1682 if not len(repo):
1683 if not len(repo):
1683 return [], None, None
1684 return [], None, None
1684 limit = loglimit(opts)
1685 limit = loglimit(opts)
1685 # Default --rev value depends on --follow but --follow behaviour
1686 # Default --rev value depends on --follow but --follow behaviour
1686 # depends on revisions resolved from --rev...
1687 # depends on revisions resolved from --rev...
1687 follow = opts.get('follow') or opts.get('follow_first')
1688 follow = opts.get('follow') or opts.get('follow_first')
1688 possiblyunsorted = False # whether revs might need sorting
1689 possiblyunsorted = False # whether revs might need sorting
1689 if opts.get('rev'):
1690 if opts.get('rev'):
1690 revs = scmutil.revrange(repo, opts['rev'])
1691 revs = scmutil.revrange(repo, opts['rev'])
1691 # Don't sort here because _makelogrevset might depend on the
1692 # Don't sort here because _makelogrevset might depend on the
1692 # order of revs
1693 # order of revs
1693 possiblyunsorted = True
1694 possiblyunsorted = True
1694 else:
1695 else:
1695 if follow and len(repo) > 0:
1696 if follow and len(repo) > 0:
1696 revs = repo.revs('reverse(:.)')
1697 revs = repo.revs('reverse(:.)')
1697 else:
1698 else:
1698 revs = revset.spanset(repo)
1699 revs = revset.spanset(repo)
1699 revs.reverse()
1700 revs.reverse()
1700 if not revs:
1701 if not revs:
1701 return revset.baseset(), None, None
1702 return revset.baseset(), None, None
1702 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1703 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1703 if possiblyunsorted:
1704 if possiblyunsorted:
1704 revs.sort(reverse=True)
1705 revs.sort(reverse=True)
1705 if expr:
1706 if expr:
1706 # Revset matchers often operate faster on revisions in changelog
1707 # Revset matchers often operate faster on revisions in changelog
1707 # order, because most filters deal with the changelog.
1708 # order, because most filters deal with the changelog.
1708 revs.reverse()
1709 revs.reverse()
1709 matcher = revset.match(repo.ui, expr)
1710 matcher = revset.match(repo.ui, expr)
1710 # Revset matches can reorder revisions. "A or B" typically returns
1711 # Revset matches can reorder revisions. "A or B" typically returns
1711 # returns the revision matching A then the revision matching B. Sort
1712 # returns the revision matching A then the revision matching B. Sort
1712 # again to fix that.
1713 # again to fix that.
1713 revs = matcher(repo, revs)
1714 revs = matcher(repo, revs)
1714 revs.sort(reverse=True)
1715 revs.sort(reverse=True)
1715 if limit is not None:
1716 if limit is not None:
1716 limitedrevs = revset.baseset()
1717 limitedrevs = revset.baseset()
1717 for idx, rev in enumerate(revs):
1718 for idx, rev in enumerate(revs):
1718 if idx >= limit:
1719 if idx >= limit:
1719 break
1720 break
1720 limitedrevs.append(rev)
1721 limitedrevs.append(rev)
1721 revs = limitedrevs
1722 revs = limitedrevs
1722
1723
1723 return revs, expr, filematcher
1724 return revs, expr, filematcher
1724
1725
1725 def getlogrevs(repo, pats, opts):
1726 def getlogrevs(repo, pats, opts):
1726 """Return (revs, expr, filematcher) where revs is an iterable of
1727 """Return (revs, expr, filematcher) where revs is an iterable of
1727 revision numbers, expr is a revset string built from log options
1728 revision numbers, expr is a revset string built from log options
1728 and file patterns or None, and used to filter 'revs'. If --stat or
1729 and file patterns or None, and used to filter 'revs'. If --stat or
1729 --patch are not passed filematcher is None. Otherwise it is a
1730 --patch are not passed filematcher is None. Otherwise it is a
1730 callable taking a revision number and returning a match objects
1731 callable taking a revision number and returning a match objects
1731 filtering the files to be detailed when displaying the revision.
1732 filtering the files to be detailed when displaying the revision.
1732 """
1733 """
1733 limit = loglimit(opts)
1734 limit = loglimit(opts)
1734 # Default --rev value depends on --follow but --follow behaviour
1735 # Default --rev value depends on --follow but --follow behaviour
1735 # depends on revisions resolved from --rev...
1736 # depends on revisions resolved from --rev...
1736 follow = opts.get('follow') or opts.get('follow_first')
1737 follow = opts.get('follow') or opts.get('follow_first')
1737 if opts.get('rev'):
1738 if opts.get('rev'):
1738 revs = scmutil.revrange(repo, opts['rev'])
1739 revs = scmutil.revrange(repo, opts['rev'])
1739 elif follow:
1740 elif follow:
1740 revs = repo.revs('reverse(:.)')
1741 revs = repo.revs('reverse(:.)')
1741 else:
1742 else:
1742 revs = revset.spanset(repo)
1743 revs = revset.spanset(repo)
1743 revs.reverse()
1744 revs.reverse()
1744 if not revs:
1745 if not revs:
1745 return revset.baseset([]), None, None
1746 return revset.baseset([]), None, None
1746 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1747 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1747 if expr:
1748 if expr:
1748 # Revset matchers often operate faster on revisions in changelog
1749 # Revset matchers often operate faster on revisions in changelog
1749 # order, because most filters deal with the changelog.
1750 # order, because most filters deal with the changelog.
1750 if not opts.get('rev'):
1751 if not opts.get('rev'):
1751 revs.reverse()
1752 revs.reverse()
1752 matcher = revset.match(repo.ui, expr)
1753 matcher = revset.match(repo.ui, expr)
1753 # Revset matches can reorder revisions. "A or B" typically returns
1754 # Revset matches can reorder revisions. "A or B" typically returns
1754 # returns the revision matching A then the revision matching B. Sort
1755 # returns the revision matching A then the revision matching B. Sort
1755 # again to fix that.
1756 # again to fix that.
1756 revs = matcher(repo, revs)
1757 revs = matcher(repo, revs)
1757 if not opts.get('rev'):
1758 if not opts.get('rev'):
1758 revs.sort(reverse=True)
1759 revs.sort(reverse=True)
1759 if limit is not None:
1760 if limit is not None:
1760 count = 0
1761 count = 0
1761 limitedrevs = revset.baseset([])
1762 limitedrevs = revset.baseset([])
1762 it = iter(revs)
1763 it = iter(revs)
1763 while count < limit:
1764 while count < limit:
1764 try:
1765 try:
1765 limitedrevs.append(it.next())
1766 limitedrevs.append(it.next())
1766 except (StopIteration):
1767 except (StopIteration):
1767 break
1768 break
1768 count += 1
1769 count += 1
1769 revs = limitedrevs
1770 revs = limitedrevs
1770
1771
1771 return revs, expr, filematcher
1772 return revs, expr, filematcher
1772
1773
1773 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1774 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1774 filematcher=None):
1775 filematcher=None):
1775 seen, state = [], graphmod.asciistate()
1776 seen, state = [], graphmod.asciistate()
1776 for rev, type, ctx, parents in dag:
1777 for rev, type, ctx, parents in dag:
1777 char = 'o'
1778 char = 'o'
1778 if ctx.node() in showparents:
1779 if ctx.node() in showparents:
1779 char = '@'
1780 char = '@'
1780 elif ctx.obsolete():
1781 elif ctx.obsolete():
1781 char = 'x'
1782 char = 'x'
1782 copies = None
1783 copies = None
1783 if getrenamed and ctx.rev():
1784 if getrenamed and ctx.rev():
1784 copies = []
1785 copies = []
1785 for fn in ctx.files():
1786 for fn in ctx.files():
1786 rename = getrenamed(fn, ctx.rev())
1787 rename = getrenamed(fn, ctx.rev())
1787 if rename:
1788 if rename:
1788 copies.append((fn, rename[0]))
1789 copies.append((fn, rename[0]))
1789 revmatchfn = None
1790 revmatchfn = None
1790 if filematcher is not None:
1791 if filematcher is not None:
1791 revmatchfn = filematcher(ctx.rev())
1792 revmatchfn = filematcher(ctx.rev())
1792 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1793 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1793 lines = displayer.hunk.pop(rev).split('\n')
1794 lines = displayer.hunk.pop(rev).split('\n')
1794 if not lines[-1]:
1795 if not lines[-1]:
1795 del lines[-1]
1796 del lines[-1]
1796 displayer.flush(rev)
1797 displayer.flush(rev)
1797 edges = edgefn(type, char, lines, seen, rev, parents)
1798 edges = edgefn(type, char, lines, seen, rev, parents)
1798 for type, char, lines, coldata in edges:
1799 for type, char, lines, coldata in edges:
1799 graphmod.ascii(ui, state, type, char, lines, coldata)
1800 graphmod.ascii(ui, state, type, char, lines, coldata)
1800 displayer.close()
1801 displayer.close()
1801
1802
1802 def graphlog(ui, repo, *pats, **opts):
1803 def graphlog(ui, repo, *pats, **opts):
1803 # Parameters are identical to log command ones
1804 # Parameters are identical to log command ones
1804 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1805 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1805 revdag = graphmod.dagwalker(repo, revs)
1806 revdag = graphmod.dagwalker(repo, revs)
1806
1807
1807 getrenamed = None
1808 getrenamed = None
1808 if opts.get('copies'):
1809 if opts.get('copies'):
1809 endrev = None
1810 endrev = None
1810 if opts.get('rev'):
1811 if opts.get('rev'):
1811 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1812 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1812 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1813 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1813 displayer = show_changeset(ui, repo, opts, buffered=True)
1814 displayer = show_changeset(ui, repo, opts, buffered=True)
1814 showparents = [ctx.node() for ctx in repo[None].parents()]
1815 showparents = [ctx.node() for ctx in repo[None].parents()]
1815 displaygraph(ui, revdag, displayer, showparents,
1816 displaygraph(ui, revdag, displayer, showparents,
1816 graphmod.asciiedges, getrenamed, filematcher)
1817 graphmod.asciiedges, getrenamed, filematcher)
1817
1818
1818 def checkunsupportedgraphflags(pats, opts):
1819 def checkunsupportedgraphflags(pats, opts):
1819 for op in ["newest_first"]:
1820 for op in ["newest_first"]:
1820 if op in opts and opts[op]:
1821 if op in opts and opts[op]:
1821 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1822 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1822 % op.replace("_", "-"))
1823 % op.replace("_", "-"))
1823
1824
1824 def graphrevs(repo, nodes, opts):
1825 def graphrevs(repo, nodes, opts):
1825 limit = loglimit(opts)
1826 limit = loglimit(opts)
1826 nodes.reverse()
1827 nodes.reverse()
1827 if limit is not None:
1828 if limit is not None:
1828 nodes = nodes[:limit]
1829 nodes = nodes[:limit]
1829 return graphmod.nodes(repo, nodes)
1830 return graphmod.nodes(repo, nodes)
1830
1831
1831 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1832 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1832 join = lambda f: os.path.join(prefix, f)
1833 join = lambda f: os.path.join(prefix, f)
1833 bad = []
1834 bad = []
1834 oldbad = match.bad
1835 oldbad = match.bad
1835 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1836 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1836 names = []
1837 names = []
1837 wctx = repo[None]
1838 wctx = repo[None]
1838 cca = None
1839 cca = None
1839 abort, warn = scmutil.checkportabilityalert(ui)
1840 abort, warn = scmutil.checkportabilityalert(ui)
1840 if abort or warn:
1841 if abort or warn:
1841 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1842 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1842 for f in repo.walk(match):
1843 for f in repo.walk(match):
1843 exact = match.exact(f)
1844 exact = match.exact(f)
1844 if exact or not explicitonly and f not in repo.dirstate:
1845 if exact or not explicitonly and f not in repo.dirstate:
1845 if cca:
1846 if cca:
1846 cca(f)
1847 cca(f)
1847 names.append(f)
1848 names.append(f)
1848 if ui.verbose or not exact:
1849 if ui.verbose or not exact:
1849 ui.status(_('adding %s\n') % match.rel(join(f)))
1850 ui.status(_('adding %s\n') % match.rel(join(f)))
1850
1851
1851 for subpath in sorted(wctx.substate):
1852 for subpath in sorted(wctx.substate):
1852 sub = wctx.sub(subpath)
1853 sub = wctx.sub(subpath)
1853 try:
1854 try:
1854 submatch = matchmod.narrowmatcher(subpath, match)
1855 submatch = matchmod.narrowmatcher(subpath, match)
1855 if listsubrepos:
1856 if listsubrepos:
1856 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1857 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1857 False))
1858 False))
1858 else:
1859 else:
1859 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1860 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1860 True))
1861 True))
1861 except error.LookupError:
1862 except error.LookupError:
1862 ui.status(_("skipping missing subrepository: %s\n")
1863 ui.status(_("skipping missing subrepository: %s\n")
1863 % join(subpath))
1864 % join(subpath))
1864
1865
1865 if not dryrun:
1866 if not dryrun:
1866 rejected = wctx.add(names, prefix)
1867 rejected = wctx.add(names, prefix)
1867 bad.extend(f for f in rejected if f in match.files())
1868 bad.extend(f for f in rejected if f in match.files())
1868 return bad
1869 return bad
1869
1870
1870 def forget(ui, repo, match, prefix, explicitonly):
1871 def forget(ui, repo, match, prefix, explicitonly):
1871 join = lambda f: os.path.join(prefix, f)
1872 join = lambda f: os.path.join(prefix, f)
1872 bad = []
1873 bad = []
1873 oldbad = match.bad
1874 oldbad = match.bad
1874 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1875 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1875 wctx = repo[None]
1876 wctx = repo[None]
1876 forgot = []
1877 forgot = []
1877 s = repo.status(match=match, clean=True)
1878 s = repo.status(match=match, clean=True)
1878 forget = sorted(s[0] + s[1] + s[3] + s[6])
1879 forget = sorted(s[0] + s[1] + s[3] + s[6])
1879 if explicitonly:
1880 if explicitonly:
1880 forget = [f for f in forget if match.exact(f)]
1881 forget = [f for f in forget if match.exact(f)]
1881
1882
1882 for subpath in sorted(wctx.substate):
1883 for subpath in sorted(wctx.substate):
1883 sub = wctx.sub(subpath)
1884 sub = wctx.sub(subpath)
1884 try:
1885 try:
1885 submatch = matchmod.narrowmatcher(subpath, match)
1886 submatch = matchmod.narrowmatcher(subpath, match)
1886 subbad, subforgot = sub.forget(ui, submatch, prefix)
1887 subbad, subforgot = sub.forget(ui, submatch, prefix)
1887 bad.extend([subpath + '/' + f for f in subbad])
1888 bad.extend([subpath + '/' + f for f in subbad])
1888 forgot.extend([subpath + '/' + f for f in subforgot])
1889 forgot.extend([subpath + '/' + f for f in subforgot])
1889 except error.LookupError:
1890 except error.LookupError:
1890 ui.status(_("skipping missing subrepository: %s\n")
1891 ui.status(_("skipping missing subrepository: %s\n")
1891 % join(subpath))
1892 % join(subpath))
1892
1893
1893 if not explicitonly:
1894 if not explicitonly:
1894 for f in match.files():
1895 for f in match.files():
1895 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1896 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1896 if f not in forgot:
1897 if f not in forgot:
1897 if os.path.exists(match.rel(join(f))):
1898 if os.path.exists(match.rel(join(f))):
1898 ui.warn(_('not removing %s: '
1899 ui.warn(_('not removing %s: '
1899 'file is already untracked\n')
1900 'file is already untracked\n')
1900 % match.rel(join(f)))
1901 % match.rel(join(f)))
1901 bad.append(f)
1902 bad.append(f)
1902
1903
1903 for f in forget:
1904 for f in forget:
1904 if ui.verbose or not match.exact(f):
1905 if ui.verbose or not match.exact(f):
1905 ui.status(_('removing %s\n') % match.rel(join(f)))
1906 ui.status(_('removing %s\n') % match.rel(join(f)))
1906
1907
1907 rejected = wctx.forget(forget, prefix)
1908 rejected = wctx.forget(forget, prefix)
1908 bad.extend(f for f in rejected if f in match.files())
1909 bad.extend(f for f in rejected if f in match.files())
1909 forgot.extend(forget)
1910 forgot.extend(forget)
1910 return bad, forgot
1911 return bad, forgot
1911
1912
1912 def cat(ui, repo, ctx, matcher, prefix, **opts):
1913 def cat(ui, repo, ctx, matcher, prefix, **opts):
1913 err = 1
1914 err = 1
1914
1915
1915 def write(path):
1916 def write(path):
1916 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1917 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1917 pathname=os.path.join(prefix, path))
1918 pathname=os.path.join(prefix, path))
1918 data = ctx[path].data()
1919 data = ctx[path].data()
1919 if opts.get('decode'):
1920 if opts.get('decode'):
1920 data = repo.wwritedata(path, data)
1921 data = repo.wwritedata(path, data)
1921 fp.write(data)
1922 fp.write(data)
1922 fp.close()
1923 fp.close()
1923
1924
1924 # Automation often uses hg cat on single files, so special case it
1925 # Automation often uses hg cat on single files, so special case it
1925 # for performance to avoid the cost of parsing the manifest.
1926 # for performance to avoid the cost of parsing the manifest.
1926 if len(matcher.files()) == 1 and not matcher.anypats():
1927 if len(matcher.files()) == 1 and not matcher.anypats():
1927 file = matcher.files()[0]
1928 file = matcher.files()[0]
1928 mf = repo.manifest
1929 mf = repo.manifest
1929 mfnode = ctx._changeset[0]
1930 mfnode = ctx._changeset[0]
1930 if mf.find(mfnode, file)[0]:
1931 if mf.find(mfnode, file)[0]:
1931 write(file)
1932 write(file)
1932 return 0
1933 return 0
1933
1934
1934 # Don't warn about "missing" files that are really in subrepos
1935 # Don't warn about "missing" files that are really in subrepos
1935 bad = matcher.bad
1936 bad = matcher.bad
1936
1937
1937 def badfn(path, msg):
1938 def badfn(path, msg):
1938 for subpath in ctx.substate:
1939 for subpath in ctx.substate:
1939 if path.startswith(subpath):
1940 if path.startswith(subpath):
1940 return
1941 return
1941 bad(path, msg)
1942 bad(path, msg)
1942
1943
1943 matcher.bad = badfn
1944 matcher.bad = badfn
1944
1945
1945 for abs in ctx.walk(matcher):
1946 for abs in ctx.walk(matcher):
1946 write(abs)
1947 write(abs)
1947 err = 0
1948 err = 0
1948
1949
1949 matcher.bad = bad
1950 matcher.bad = bad
1950
1951
1951 for subpath in sorted(ctx.substate):
1952 for subpath in sorted(ctx.substate):
1952 sub = ctx.sub(subpath)
1953 sub = ctx.sub(subpath)
1953 try:
1954 try:
1954 submatch = matchmod.narrowmatcher(subpath, matcher)
1955 submatch = matchmod.narrowmatcher(subpath, matcher)
1955
1956
1956 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1957 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1957 **opts):
1958 **opts):
1958 err = 0
1959 err = 0
1959 except error.RepoLookupError:
1960 except error.RepoLookupError:
1960 ui.status(_("skipping missing subrepository: %s\n")
1961 ui.status(_("skipping missing subrepository: %s\n")
1961 % os.path.join(prefix, subpath))
1962 % os.path.join(prefix, subpath))
1962
1963
1963 return err
1964 return err
1964
1965
1965 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1966 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1966 '''reproduce copies from fromrev to rev in the dirstate
1967 '''reproduce copies from fromrev to rev in the dirstate
1967
1968
1968 If skiprev is specified, it's a revision that should be used to
1969 If skiprev is specified, it's a revision that should be used to
1969 filter copy records. Any copies that occur between fromrev and
1970 filter copy records. Any copies that occur between fromrev and
1970 skiprev will not be duplicated, even if they appear in the set of
1971 skiprev will not be duplicated, even if they appear in the set of
1971 copies between fromrev and rev.
1972 copies between fromrev and rev.
1972 '''
1973 '''
1973 exclude = {}
1974 exclude = {}
1974 if skiprev is not None:
1975 if skiprev is not None:
1975 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1976 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1976 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1977 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1977 # copies.pathcopies returns backward renames, so dst might not
1978 # copies.pathcopies returns backward renames, so dst might not
1978 # actually be in the dirstate
1979 # actually be in the dirstate
1979 if dst in exclude:
1980 if dst in exclude:
1980 continue
1981 continue
1981 if repo.dirstate[dst] in "nma":
1982 if repo.dirstate[dst] in "nma":
1982 repo.dirstate.copy(src, dst)
1983 repo.dirstate.copy(src, dst)
1983
1984
1984 def commit(ui, repo, commitfunc, pats, opts):
1985 def commit(ui, repo, commitfunc, pats, opts):
1985 '''commit the specified files or all outstanding changes'''
1986 '''commit the specified files or all outstanding changes'''
1986 date = opts.get('date')
1987 date = opts.get('date')
1987 if date:
1988 if date:
1988 opts['date'] = util.parsedate(date)
1989 opts['date'] = util.parsedate(date)
1989 message = logmessage(ui, opts)
1990 message = logmessage(ui, opts)
1990
1991
1991 # extract addremove carefully -- this function can be called from a command
1992 # extract addremove carefully -- this function can be called from a command
1992 # that doesn't support addremove
1993 # that doesn't support addremove
1993 if opts.get('addremove'):
1994 if opts.get('addremove'):
1994 scmutil.addremove(repo, pats, opts)
1995 scmutil.addremove(repo, pats, opts)
1995
1996
1996 return commitfunc(ui, repo, message,
1997 return commitfunc(ui, repo, message,
1997 scmutil.match(repo[None], pats, opts), opts)
1998 scmutil.match(repo[None], pats, opts), opts)
1998
1999
1999 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2000 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2000 ui.note(_('amending changeset %s\n') % old)
2001 ui.note(_('amending changeset %s\n') % old)
2001 base = old.p1()
2002 base = old.p1()
2002
2003
2003 wlock = lock = newid = None
2004 wlock = lock = newid = None
2004 try:
2005 try:
2005 wlock = repo.wlock()
2006 wlock = repo.wlock()
2006 lock = repo.lock()
2007 lock = repo.lock()
2007 tr = repo.transaction('amend')
2008 tr = repo.transaction('amend')
2008 try:
2009 try:
2009 # See if we got a message from -m or -l, if not, open the editor
2010 # See if we got a message from -m or -l, if not, open the editor
2010 # with the message of the changeset to amend
2011 # with the message of the changeset to amend
2011 message = logmessage(ui, opts)
2012 message = logmessage(ui, opts)
2012 # ensure logfile does not conflict with later enforcement of the
2013 # ensure logfile does not conflict with later enforcement of the
2013 # message. potential logfile content has been processed by
2014 # message. potential logfile content has been processed by
2014 # `logmessage` anyway.
2015 # `logmessage` anyway.
2015 opts.pop('logfile')
2016 opts.pop('logfile')
2016 # First, do a regular commit to record all changes in the working
2017 # First, do a regular commit to record all changes in the working
2017 # directory (if there are any)
2018 # directory (if there are any)
2018 ui.callhooks = False
2019 ui.callhooks = False
2019 currentbookmark = repo._bookmarkcurrent
2020 currentbookmark = repo._bookmarkcurrent
2020 try:
2021 try:
2021 repo._bookmarkcurrent = None
2022 repo._bookmarkcurrent = None
2022 opts['message'] = 'temporary amend commit for %s' % old
2023 opts['message'] = 'temporary amend commit for %s' % old
2023 node = commit(ui, repo, commitfunc, pats, opts)
2024 node = commit(ui, repo, commitfunc, pats, opts)
2024 finally:
2025 finally:
2025 repo._bookmarkcurrent = currentbookmark
2026 repo._bookmarkcurrent = currentbookmark
2026 ui.callhooks = True
2027 ui.callhooks = True
2027 ctx = repo[node]
2028 ctx = repo[node]
2028
2029
2029 # Participating changesets:
2030 # Participating changesets:
2030 #
2031 #
2031 # node/ctx o - new (intermediate) commit that contains changes
2032 # node/ctx o - new (intermediate) commit that contains changes
2032 # | from working dir to go into amending commit
2033 # | from working dir to go into amending commit
2033 # | (or a workingctx if there were no changes)
2034 # | (or a workingctx if there were no changes)
2034 # |
2035 # |
2035 # old o - changeset to amend
2036 # old o - changeset to amend
2036 # |
2037 # |
2037 # base o - parent of amending changeset
2038 # base o - parent of amending changeset
2038
2039
2039 # Update extra dict from amended commit (e.g. to preserve graft
2040 # Update extra dict from amended commit (e.g. to preserve graft
2040 # source)
2041 # source)
2041 extra.update(old.extra())
2042 extra.update(old.extra())
2042
2043
2043 # Also update it from the intermediate commit or from the wctx
2044 # Also update it from the intermediate commit or from the wctx
2044 extra.update(ctx.extra())
2045 extra.update(ctx.extra())
2045
2046
2046 if len(old.parents()) > 1:
2047 if len(old.parents()) > 1:
2047 # ctx.files() isn't reliable for merges, so fall back to the
2048 # ctx.files() isn't reliable for merges, so fall back to the
2048 # slower repo.status() method
2049 # slower repo.status() method
2049 files = set([fn for st in repo.status(base, old)[:3]
2050 files = set([fn for st in repo.status(base, old)[:3]
2050 for fn in st])
2051 for fn in st])
2051 else:
2052 else:
2052 files = set(old.files())
2053 files = set(old.files())
2053
2054
2054 # Second, we use either the commit we just did, or if there were no
2055 # Second, we use either the commit we just did, or if there were no
2055 # changes the parent of the working directory as the version of the
2056 # changes the parent of the working directory as the version of the
2056 # files in the final amend commit
2057 # files in the final amend commit
2057 if node:
2058 if node:
2058 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2059 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2059
2060
2060 user = ctx.user()
2061 user = ctx.user()
2061 date = ctx.date()
2062 date = ctx.date()
2062 # Recompute copies (avoid recording a -> b -> a)
2063 # Recompute copies (avoid recording a -> b -> a)
2063 copied = copies.pathcopies(base, ctx)
2064 copied = copies.pathcopies(base, ctx)
2064
2065
2065 # Prune files which were reverted by the updates: if old
2066 # Prune files which were reverted by the updates: if old
2066 # introduced file X and our intermediate commit, node,
2067 # introduced file X and our intermediate commit, node,
2067 # renamed that file, then those two files are the same and
2068 # renamed that file, then those two files are the same and
2068 # we can discard X from our list of files. Likewise if X
2069 # we can discard X from our list of files. Likewise if X
2069 # was deleted, it's no longer relevant
2070 # was deleted, it's no longer relevant
2070 files.update(ctx.files())
2071 files.update(ctx.files())
2071
2072
2072 def samefile(f):
2073 def samefile(f):
2073 if f in ctx.manifest():
2074 if f in ctx.manifest():
2074 a = ctx.filectx(f)
2075 a = ctx.filectx(f)
2075 if f in base.manifest():
2076 if f in base.manifest():
2076 b = base.filectx(f)
2077 b = base.filectx(f)
2077 return (not a.cmp(b)
2078 return (not a.cmp(b)
2078 and a.flags() == b.flags())
2079 and a.flags() == b.flags())
2079 else:
2080 else:
2080 return False
2081 return False
2081 else:
2082 else:
2082 return f not in base.manifest()
2083 return f not in base.manifest()
2083 files = [f for f in files if not samefile(f)]
2084 files = [f for f in files if not samefile(f)]
2084
2085
2085 def filectxfn(repo, ctx_, path):
2086 def filectxfn(repo, ctx_, path):
2086 try:
2087 try:
2087 fctx = ctx[path]
2088 fctx = ctx[path]
2088 flags = fctx.flags()
2089 flags = fctx.flags()
2089 mctx = context.memfilectx(repo,
2090 mctx = context.memfilectx(repo,
2090 fctx.path(), fctx.data(),
2091 fctx.path(), fctx.data(),
2091 islink='l' in flags,
2092 islink='l' in flags,
2092 isexec='x' in flags,
2093 isexec='x' in flags,
2093 copied=copied.get(path))
2094 copied=copied.get(path))
2094 return mctx
2095 return mctx
2095 except KeyError:
2096 except KeyError:
2096 raise IOError
2097 raise IOError
2097 else:
2098 else:
2098 ui.note(_('copying changeset %s to %s\n') % (old, base))
2099 ui.note(_('copying changeset %s to %s\n') % (old, base))
2099
2100
2100 # Use version of files as in the old cset
2101 # Use version of files as in the old cset
2101 def filectxfn(repo, ctx_, path):
2102 def filectxfn(repo, ctx_, path):
2102 try:
2103 try:
2103 return old.filectx(path)
2104 return old.filectx(path)
2104 except KeyError:
2105 except KeyError:
2105 raise IOError
2106 raise IOError
2106
2107
2107 user = opts.get('user') or old.user()
2108 user = opts.get('user') or old.user()
2108 date = opts.get('date') or old.date()
2109 date = opts.get('date') or old.date()
2109 editform = 'commit.amend'
2110 editform = 'commit.amend'
2110 editor = getcommiteditor(editform=editform, **opts)
2111 editor = getcommiteditor(editform=editform, **opts)
2111 if not message:
2112 if not message:
2112 editor = getcommiteditor(edit=True, editform=editform)
2113 editor = getcommiteditor(edit=True, editform=editform)
2113 message = old.description()
2114 message = old.description()
2114
2115
2115 pureextra = extra.copy()
2116 pureextra = extra.copy()
2116 extra['amend_source'] = old.hex()
2117 extra['amend_source'] = old.hex()
2117
2118
2118 new = context.memctx(repo,
2119 new = context.memctx(repo,
2119 parents=[base.node(), old.p2().node()],
2120 parents=[base.node(), old.p2().node()],
2120 text=message,
2121 text=message,
2121 files=files,
2122 files=files,
2122 filectxfn=filectxfn,
2123 filectxfn=filectxfn,
2123 user=user,
2124 user=user,
2124 date=date,
2125 date=date,
2125 extra=extra,
2126 extra=extra,
2126 editor=editor)
2127 editor=editor)
2127
2128
2128 newdesc = changelog.stripdesc(new.description())
2129 newdesc = changelog.stripdesc(new.description())
2129 if ((not node)
2130 if ((not node)
2130 and newdesc == old.description()
2131 and newdesc == old.description()
2131 and user == old.user()
2132 and user == old.user()
2132 and date == old.date()
2133 and date == old.date()
2133 and pureextra == old.extra()):
2134 and pureextra == old.extra()):
2134 # nothing changed. continuing here would create a new node
2135 # nothing changed. continuing here would create a new node
2135 # anyway because of the amend_source noise.
2136 # anyway because of the amend_source noise.
2136 #
2137 #
2137 # This not what we expect from amend.
2138 # This not what we expect from amend.
2138 return old.node()
2139 return old.node()
2139
2140
2140 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2141 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2141 try:
2142 try:
2142 if opts.get('secret'):
2143 if opts.get('secret'):
2143 commitphase = 'secret'
2144 commitphase = 'secret'
2144 else:
2145 else:
2145 commitphase = old.phase()
2146 commitphase = old.phase()
2146 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2147 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2147 newid = repo.commitctx(new)
2148 newid = repo.commitctx(new)
2148 finally:
2149 finally:
2149 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2150 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2150 if newid != old.node():
2151 if newid != old.node():
2151 # Reroute the working copy parent to the new changeset
2152 # Reroute the working copy parent to the new changeset
2152 repo.setparents(newid, nullid)
2153 repo.setparents(newid, nullid)
2153
2154
2154 # Move bookmarks from old parent to amend commit
2155 # Move bookmarks from old parent to amend commit
2155 bms = repo.nodebookmarks(old.node())
2156 bms = repo.nodebookmarks(old.node())
2156 if bms:
2157 if bms:
2157 marks = repo._bookmarks
2158 marks = repo._bookmarks
2158 for bm in bms:
2159 for bm in bms:
2159 marks[bm] = newid
2160 marks[bm] = newid
2160 marks.write()
2161 marks.write()
2161 #commit the whole amend process
2162 #commit the whole amend process
2162 if obsolete._enabled and newid != old.node():
2163 if obsolete._enabled and newid != old.node():
2163 # mark the new changeset as successor of the rewritten one
2164 # mark the new changeset as successor of the rewritten one
2164 new = repo[newid]
2165 new = repo[newid]
2165 obs = [(old, (new,))]
2166 obs = [(old, (new,))]
2166 if node:
2167 if node:
2167 obs.append((ctx, ()))
2168 obs.append((ctx, ()))
2168
2169
2169 obsolete.createmarkers(repo, obs)
2170 obsolete.createmarkers(repo, obs)
2170 tr.close()
2171 tr.close()
2171 finally:
2172 finally:
2172 tr.release()
2173 tr.release()
2173 if (not obsolete._enabled) and newid != old.node():
2174 if (not obsolete._enabled) and newid != old.node():
2174 # Strip the intermediate commit (if there was one) and the amended
2175 # Strip the intermediate commit (if there was one) and the amended
2175 # commit
2176 # commit
2176 if node:
2177 if node:
2177 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2178 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2178 ui.note(_('stripping amended changeset %s\n') % old)
2179 ui.note(_('stripping amended changeset %s\n') % old)
2179 repair.strip(ui, repo, old.node(), topic='amend-backup')
2180 repair.strip(ui, repo, old.node(), topic='amend-backup')
2180 finally:
2181 finally:
2181 if newid is None:
2182 if newid is None:
2182 repo.dirstate.invalidate()
2183 repo.dirstate.invalidate()
2183 lockmod.release(lock, wlock)
2184 lockmod.release(lock, wlock)
2184 return newid
2185 return newid
2185
2186
2186 def commiteditor(repo, ctx, subs, editform=''):
2187 def commiteditor(repo, ctx, subs, editform=''):
2187 if ctx.description():
2188 if ctx.description():
2188 return ctx.description()
2189 return ctx.description()
2189 return commitforceeditor(repo, ctx, subs, editform=editform)
2190 return commitforceeditor(repo, ctx, subs, editform=editform)
2190
2191
2191 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2192 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2192 editform=''):
2193 editform=''):
2193 if not extramsg:
2194 if not extramsg:
2194 extramsg = _("Leave message empty to abort commit.")
2195 extramsg = _("Leave message empty to abort commit.")
2195 tmpl = repo.ui.config('committemplate', 'changeset', '').strip()
2196 tmpl = repo.ui.config('committemplate', 'changeset', '').strip()
2196 if tmpl:
2197 if tmpl:
2197 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2198 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2198 else:
2199 else:
2199 committext = buildcommittext(repo, ctx, subs, extramsg)
2200 committext = buildcommittext(repo, ctx, subs, extramsg)
2200
2201
2201 # run editor in the repository root
2202 # run editor in the repository root
2202 olddir = os.getcwd()
2203 olddir = os.getcwd()
2203 os.chdir(repo.root)
2204 os.chdir(repo.root)
2204 text = repo.ui.edit(committext, ctx.user(), ctx.extra())
2205 text = repo.ui.edit(committext, ctx.user(), ctx.extra())
2205 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2206 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2206 os.chdir(olddir)
2207 os.chdir(olddir)
2207
2208
2208 if finishdesc:
2209 if finishdesc:
2209 text = finishdesc(text)
2210 text = finishdesc(text)
2210 if not text.strip():
2211 if not text.strip():
2211 raise util.Abort(_("empty commit message"))
2212 raise util.Abort(_("empty commit message"))
2212
2213
2213 return text
2214 return text
2214
2215
2215 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2216 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2216 ui = repo.ui
2217 ui = repo.ui
2217 tmpl, mapfile = gettemplate(ui, tmpl, None)
2218 tmpl, mapfile = gettemplate(ui, tmpl, None)
2218
2219
2219 try:
2220 try:
2220 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2221 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2221 except SyntaxError, inst:
2222 except SyntaxError, inst:
2222 raise util.Abort(inst.args[0])
2223 raise util.Abort(inst.args[0])
2223
2224
2224 if not extramsg:
2225 if not extramsg:
2225 extramsg = '' # ensure that extramsg is string
2226 extramsg = '' # ensure that extramsg is string
2226
2227
2227 ui.pushbuffer()
2228 ui.pushbuffer()
2228 t.show(ctx, extramsg=extramsg)
2229 t.show(ctx, extramsg=extramsg)
2229 return ui.popbuffer()
2230 return ui.popbuffer()
2230
2231
2231 def buildcommittext(repo, ctx, subs, extramsg):
2232 def buildcommittext(repo, ctx, subs, extramsg):
2232 edittext = []
2233 edittext = []
2233 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2234 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2234 if ctx.description():
2235 if ctx.description():
2235 edittext.append(ctx.description())
2236 edittext.append(ctx.description())
2236 edittext.append("")
2237 edittext.append("")
2237 edittext.append("") # Empty line between message and comments.
2238 edittext.append("") # Empty line between message and comments.
2238 edittext.append(_("HG: Enter commit message."
2239 edittext.append(_("HG: Enter commit message."
2239 " Lines beginning with 'HG:' are removed."))
2240 " Lines beginning with 'HG:' are removed."))
2240 edittext.append("HG: %s" % extramsg)
2241 edittext.append("HG: %s" % extramsg)
2241 edittext.append("HG: --")
2242 edittext.append("HG: --")
2242 edittext.append(_("HG: user: %s") % ctx.user())
2243 edittext.append(_("HG: user: %s") % ctx.user())
2243 if ctx.p2():
2244 if ctx.p2():
2244 edittext.append(_("HG: branch merge"))
2245 edittext.append(_("HG: branch merge"))
2245 if ctx.branch():
2246 if ctx.branch():
2246 edittext.append(_("HG: branch '%s'") % ctx.branch())
2247 edittext.append(_("HG: branch '%s'") % ctx.branch())
2247 if bookmarks.iscurrent(repo):
2248 if bookmarks.iscurrent(repo):
2248 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2249 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2249 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2250 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2250 edittext.extend([_("HG: added %s") % f for f in added])
2251 edittext.extend([_("HG: added %s") % f for f in added])
2251 edittext.extend([_("HG: changed %s") % f for f in modified])
2252 edittext.extend([_("HG: changed %s") % f for f in modified])
2252 edittext.extend([_("HG: removed %s") % f for f in removed])
2253 edittext.extend([_("HG: removed %s") % f for f in removed])
2253 if not added and not modified and not removed:
2254 if not added and not modified and not removed:
2254 edittext.append(_("HG: no files changed"))
2255 edittext.append(_("HG: no files changed"))
2255 edittext.append("")
2256 edittext.append("")
2256
2257
2257 return "\n".join(edittext)
2258 return "\n".join(edittext)
2258
2259
2259 def commitstatus(repo, node, branch, bheads=None, opts={}):
2260 def commitstatus(repo, node, branch, bheads=None, opts={}):
2260 ctx = repo[node]
2261 ctx = repo[node]
2261 parents = ctx.parents()
2262 parents = ctx.parents()
2262
2263
2263 if (not opts.get('amend') and bheads and node not in bheads and not
2264 if (not opts.get('amend') and bheads and node not in bheads and not
2264 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2265 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2265 repo.ui.status(_('created new head\n'))
2266 repo.ui.status(_('created new head\n'))
2266 # The message is not printed for initial roots. For the other
2267 # The message is not printed for initial roots. For the other
2267 # changesets, it is printed in the following situations:
2268 # changesets, it is printed in the following situations:
2268 #
2269 #
2269 # Par column: for the 2 parents with ...
2270 # Par column: for the 2 parents with ...
2270 # N: null or no parent
2271 # N: null or no parent
2271 # B: parent is on another named branch
2272 # B: parent is on another named branch
2272 # C: parent is a regular non head changeset
2273 # C: parent is a regular non head changeset
2273 # H: parent was a branch head of the current branch
2274 # H: parent was a branch head of the current branch
2274 # Msg column: whether we print "created new head" message
2275 # Msg column: whether we print "created new head" message
2275 # In the following, it is assumed that there already exists some
2276 # In the following, it is assumed that there already exists some
2276 # initial branch heads of the current branch, otherwise nothing is
2277 # initial branch heads of the current branch, otherwise nothing is
2277 # printed anyway.
2278 # printed anyway.
2278 #
2279 #
2279 # Par Msg Comment
2280 # Par Msg Comment
2280 # N N y additional topo root
2281 # N N y additional topo root
2281 #
2282 #
2282 # B N y additional branch root
2283 # B N y additional branch root
2283 # C N y additional topo head
2284 # C N y additional topo head
2284 # H N n usual case
2285 # H N n usual case
2285 #
2286 #
2286 # B B y weird additional branch root
2287 # B B y weird additional branch root
2287 # C B y branch merge
2288 # C B y branch merge
2288 # H B n merge with named branch
2289 # H B n merge with named branch
2289 #
2290 #
2290 # C C y additional head from merge
2291 # C C y additional head from merge
2291 # C H n merge with a head
2292 # C H n merge with a head
2292 #
2293 #
2293 # H H n head merge: head count decreases
2294 # H H n head merge: head count decreases
2294
2295
2295 if not opts.get('close_branch'):
2296 if not opts.get('close_branch'):
2296 for r in parents:
2297 for r in parents:
2297 if r.closesbranch() and r.branch() == branch:
2298 if r.closesbranch() and r.branch() == branch:
2298 repo.ui.status(_('reopening closed branch head %d\n') % r)
2299 repo.ui.status(_('reopening closed branch head %d\n') % r)
2299
2300
2300 if repo.ui.debugflag:
2301 if repo.ui.debugflag:
2301 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2302 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2302 elif repo.ui.verbose:
2303 elif repo.ui.verbose:
2303 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2304 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2304
2305
2305 def revert(ui, repo, ctx, parents, *pats, **opts):
2306 def revert(ui, repo, ctx, parents, *pats, **opts):
2306 parent, p2 = parents
2307 parent, p2 = parents
2307 node = ctx.node()
2308 node = ctx.node()
2308
2309
2309 mf = ctx.manifest()
2310 mf = ctx.manifest()
2310 if node == p2:
2311 if node == p2:
2311 parent = p2
2312 parent = p2
2312 if node == parent:
2313 if node == parent:
2313 pmf = mf
2314 pmf = mf
2314 else:
2315 else:
2315 pmf = None
2316 pmf = None
2316
2317
2317 # need all matching names in dirstate and manifest of target rev,
2318 # need all matching names in dirstate and manifest of target rev,
2318 # so have to walk both. do not print errors if files exist in one
2319 # so have to walk both. do not print errors if files exist in one
2319 # but not other.
2320 # but not other.
2320
2321
2321 # `names` is a mapping for all elements in working copy and target revision
2322 # `names` is a mapping for all elements in working copy and target revision
2322 # The mapping is in the form:
2323 # The mapping is in the form:
2323 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2324 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2324 names = {}
2325 names = {}
2325
2326
2326 wlock = repo.wlock()
2327 wlock = repo.wlock()
2327 try:
2328 try:
2328 ## filling of the `names` mapping
2329 ## filling of the `names` mapping
2329 # walk dirstate to fill `names`
2330 # walk dirstate to fill `names`
2330
2331
2331 m = scmutil.match(repo[None], pats, opts)
2332 m = scmutil.match(repo[None], pats, opts)
2332 m.bad = lambda x, y: False
2333 m.bad = lambda x, y: False
2333 for abs in repo.walk(m):
2334 for abs in repo.walk(m):
2334 names[abs] = m.rel(abs), m.exact(abs)
2335 names[abs] = m.rel(abs), m.exact(abs)
2335
2336
2336 # walk target manifest to fill `names`
2337 # walk target manifest to fill `names`
2337
2338
2338 def badfn(path, msg):
2339 def badfn(path, msg):
2339 if path in names:
2340 if path in names:
2340 return
2341 return
2341 if path in ctx.substate:
2342 if path in ctx.substate:
2342 return
2343 return
2343 path_ = path + '/'
2344 path_ = path + '/'
2344 for f in names:
2345 for f in names:
2345 if f.startswith(path_):
2346 if f.startswith(path_):
2346 return
2347 return
2347 ui.warn("%s: %s\n" % (m.rel(path), msg))
2348 ui.warn("%s: %s\n" % (m.rel(path), msg))
2348
2349
2349 m = scmutil.match(ctx, pats, opts)
2350 m = scmutil.match(ctx, pats, opts)
2350 m.bad = badfn
2351 m.bad = badfn
2351 for abs in ctx.walk(m):
2352 for abs in ctx.walk(m):
2352 if abs not in names:
2353 if abs not in names:
2353 names[abs] = m.rel(abs), m.exact(abs)
2354 names[abs] = m.rel(abs), m.exact(abs)
2354
2355
2355 # get the list of subrepos that must be reverted
2356 # get the list of subrepos that must be reverted
2356 targetsubs = sorted(s for s in ctx.substate if m(s))
2357 targetsubs = sorted(s for s in ctx.substate if m(s))
2357
2358
2358 # Find status of all file in `names`. (Against working directory parent)
2359 # Find status of all file in `names`. (Against working directory parent)
2359 m = scmutil.matchfiles(repo, names)
2360 m = scmutil.matchfiles(repo, names)
2360 changes = repo.status(node1=parent, match=m)[:4]
2361 changes = repo.status(node1=parent, match=m)[:4]
2361 modified, added, removed, deleted = map(set, changes)
2362 modified, added, removed, deleted = map(set, changes)
2362
2363
2363 # if f is a rename, update `names` to also revert the source
2364 # if f is a rename, update `names` to also revert the source
2364 cwd = repo.getcwd()
2365 cwd = repo.getcwd()
2365 for f in added:
2366 for f in added:
2366 src = repo.dirstate.copied(f)
2367 src = repo.dirstate.copied(f)
2367 if src and src not in names and repo.dirstate[src] == 'r':
2368 if src and src not in names and repo.dirstate[src] == 'r':
2368 removed.add(src)
2369 removed.add(src)
2369 names[src] = (repo.pathto(src, cwd), True)
2370 names[src] = (repo.pathto(src, cwd), True)
2370
2371
2371 ## computation of the action to performs on `names` content.
2372 ## computation of the action to performs on `names` content.
2372
2373
2373 def removeforget(abs):
2374 def removeforget(abs):
2374 if repo.dirstate[abs] == 'a':
2375 if repo.dirstate[abs] == 'a':
2375 return _('forgetting %s\n')
2376 return _('forgetting %s\n')
2376 return _('removing %s\n')
2377 return _('removing %s\n')
2377
2378
2378 # action to be actually performed by revert
2379 # action to be actually performed by revert
2379 # (<list of file>, message>) tuple
2380 # (<list of file>, message>) tuple
2380 actions = {'revert': ([], _('reverting %s\n')),
2381 actions = {'revert': ([], _('reverting %s\n')),
2381 'add': ([], _('adding %s\n')),
2382 'add': ([], _('adding %s\n')),
2382 'remove': ([], removeforget),
2383 'remove': ([], removeforget),
2383 'undelete': ([], _('undeleting %s\n'))}
2384 'undelete': ([], _('undeleting %s\n'))}
2384
2385
2385 disptable = (
2386 disptable = (
2386 # dispatch table:
2387 # dispatch table:
2387 # file state
2388 # file state
2388 # action if in target manifest
2389 # action if in target manifest
2389 # action if not in target manifest
2390 # action if not in target manifest
2390 # make backup if in target manifest
2391 # make backup if in target manifest
2391 # make backup if not in target manifest
2392 # make backup if not in target manifest
2392 (modified, (actions['revert'], True),
2393 (modified, (actions['revert'], True),
2393 (actions['remove'], True)),
2394 (actions['remove'], True)),
2394 (added, (actions['revert'], True),
2395 (added, (actions['revert'], True),
2395 (actions['remove'], False)),
2396 (actions['remove'], False)),
2396 (removed, (actions['undelete'], True),
2397 (removed, (actions['undelete'], True),
2397 (None, False)),
2398 (None, False)),
2398 (deleted, (actions['revert'], False),
2399 (deleted, (actions['revert'], False),
2399 (actions['remove'], False)),
2400 (actions['remove'], False)),
2400 )
2401 )
2401
2402
2402 for abs, (rel, exact) in sorted(names.items()):
2403 for abs, (rel, exact) in sorted(names.items()):
2403 # hash on file in target manifest (or None if missing from target)
2404 # hash on file in target manifest (or None if missing from target)
2404 mfentry = mf.get(abs)
2405 mfentry = mf.get(abs)
2405 # target file to be touch on disk (relative to cwd)
2406 # target file to be touch on disk (relative to cwd)
2406 target = repo.wjoin(abs)
2407 target = repo.wjoin(abs)
2407 def handle(xlist, dobackup):
2408 def handle(xlist, dobackup):
2408 xlist[0].append(abs)
2409 xlist[0].append(abs)
2409 if (dobackup and not opts.get('no_backup') and
2410 if (dobackup and not opts.get('no_backup') and
2410 os.path.lexists(target) and
2411 os.path.lexists(target) and
2411 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2412 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2412 bakname = "%s.orig" % rel
2413 bakname = "%s.orig" % rel
2413 ui.note(_('saving current version of %s as %s\n') %
2414 ui.note(_('saving current version of %s as %s\n') %
2414 (rel, bakname))
2415 (rel, bakname))
2415 if not opts.get('dry_run'):
2416 if not opts.get('dry_run'):
2416 util.rename(target, bakname)
2417 util.rename(target, bakname)
2417 if ui.verbose or not exact:
2418 if ui.verbose or not exact:
2418 msg = xlist[1]
2419 msg = xlist[1]
2419 if not isinstance(msg, basestring):
2420 if not isinstance(msg, basestring):
2420 msg = msg(abs)
2421 msg = msg(abs)
2421 ui.status(msg % rel)
2422 ui.status(msg % rel)
2422 # search the entry in the dispatch table.
2423 # search the entry in the dispatch table.
2423 # if the file is in any of this sets, it was touched in the working
2424 # if the file is in any of this sets, it was touched in the working
2424 # directory parent and we are sure it needs to be reverted.
2425 # directory parent and we are sure it needs to be reverted.
2425 for table, hit, miss in disptable:
2426 for table, hit, miss in disptable:
2426 if abs not in table:
2427 if abs not in table:
2427 continue
2428 continue
2428 # file has changed in dirstate
2429 # file has changed in dirstate
2429 if mfentry:
2430 if mfentry:
2430 handle(*hit)
2431 handle(*hit)
2431 elif miss[0] is not None:
2432 elif miss[0] is not None:
2432 handle(*miss)
2433 handle(*miss)
2433 break
2434 break
2434 else:
2435 else:
2435 # Not touched in current dirstate.
2436 # Not touched in current dirstate.
2436
2437
2437 # file is unknown in parent, restore older version or ignore.
2438 # file is unknown in parent, restore older version or ignore.
2438 if abs not in repo.dirstate:
2439 if abs not in repo.dirstate:
2439 if mfentry:
2440 if mfentry:
2440 handle(actions['add'], True)
2441 handle(actions['add'], True)
2441 elif exact:
2442 elif exact:
2442 ui.warn(_('file not managed: %s\n') % rel)
2443 ui.warn(_('file not managed: %s\n') % rel)
2443 continue
2444 continue
2444
2445
2445 # parent is target, no changes mean no changes
2446 # parent is target, no changes mean no changes
2446 if node == parent:
2447 if node == parent:
2447 if exact:
2448 if exact:
2448 ui.warn(_('no changes needed to %s\n') % rel)
2449 ui.warn(_('no changes needed to %s\n') % rel)
2449 continue
2450 continue
2450 # no change in dirstate but parent and target may differ
2451 # no change in dirstate but parent and target may differ
2451 if pmf is None:
2452 if pmf is None:
2452 # only need parent manifest in this unlikely case,
2453 # only need parent manifest in this unlikely case,
2453 # so do not read by default
2454 # so do not read by default
2454 pmf = repo[parent].manifest()
2455 pmf = repo[parent].manifest()
2455 if abs in pmf and mfentry:
2456 if abs in pmf and mfentry:
2456 # if version of file is same in parent and target
2457 # if version of file is same in parent and target
2457 # manifests, do nothing
2458 # manifests, do nothing
2458 if (pmf[abs] != mfentry or
2459 if (pmf[abs] != mfentry or
2459 pmf.flags(abs) != mf.flags(abs)):
2460 pmf.flags(abs) != mf.flags(abs)):
2460 handle(actions['revert'], False)
2461 handle(actions['revert'], False)
2461 else:
2462 else:
2462 handle(actions['remove'], False)
2463 handle(actions['remove'], False)
2463
2464
2464 if not opts.get('dry_run'):
2465 if not opts.get('dry_run'):
2465 _performrevert(repo, parents, ctx, actions)
2466 _performrevert(repo, parents, ctx, actions)
2466
2467
2467 if targetsubs:
2468 if targetsubs:
2468 # Revert the subrepos on the revert list
2469 # Revert the subrepos on the revert list
2469 for sub in targetsubs:
2470 for sub in targetsubs:
2470 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2471 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2471 finally:
2472 finally:
2472 wlock.release()
2473 wlock.release()
2473
2474
2474 def _performrevert(repo, parents, ctx, actions):
2475 def _performrevert(repo, parents, ctx, actions):
2475 """function that actually perform all the actions computed for revert
2476 """function that actually perform all the actions computed for revert
2476
2477
2477 This is an independent function to let extension to plug in and react to
2478 This is an independent function to let extension to plug in and react to
2478 the imminent revert.
2479 the imminent revert.
2479
2480
2480 Make sure you have the working directory locked when calling this function.
2481 Make sure you have the working directory locked when calling this function.
2481 """
2482 """
2482 parent, p2 = parents
2483 parent, p2 = parents
2483 node = ctx.node()
2484 node = ctx.node()
2484 def checkout(f):
2485 def checkout(f):
2485 fc = ctx[f]
2486 fc = ctx[f]
2486 repo.wwrite(f, fc.data(), fc.flags())
2487 repo.wwrite(f, fc.data(), fc.flags())
2487
2488
2488 audit_path = pathutil.pathauditor(repo.root)
2489 audit_path = pathutil.pathauditor(repo.root)
2489 for f in actions['remove'][0]:
2490 for f in actions['remove'][0]:
2490 if repo.dirstate[f] == 'a':
2491 if repo.dirstate[f] == 'a':
2491 repo.dirstate.drop(f)
2492 repo.dirstate.drop(f)
2492 continue
2493 continue
2493 audit_path(f)
2494 audit_path(f)
2494 try:
2495 try:
2495 util.unlinkpath(repo.wjoin(f))
2496 util.unlinkpath(repo.wjoin(f))
2496 except OSError:
2497 except OSError:
2497 pass
2498 pass
2498 repo.dirstate.remove(f)
2499 repo.dirstate.remove(f)
2499
2500
2500 normal = None
2501 normal = None
2501 if node == parent:
2502 if node == parent:
2502 # We're reverting to our parent. If possible, we'd like status
2503 # We're reverting to our parent. If possible, we'd like status
2503 # to report the file as clean. We have to use normallookup for
2504 # to report the file as clean. We have to use normallookup for
2504 # merges to avoid losing information about merged/dirty files.
2505 # merges to avoid losing information about merged/dirty files.
2505 if p2 != nullid:
2506 if p2 != nullid:
2506 normal = repo.dirstate.normallookup
2507 normal = repo.dirstate.normallookup
2507 else:
2508 else:
2508 normal = repo.dirstate.normal
2509 normal = repo.dirstate.normal
2509 for f in actions['revert'][0]:
2510 for f in actions['revert'][0]:
2510 checkout(f)
2511 checkout(f)
2511 if normal:
2512 if normal:
2512 normal(f)
2513 normal(f)
2513
2514
2514 for f in actions['add'][0]:
2515 for f in actions['add'][0]:
2515 checkout(f)
2516 checkout(f)
2516 repo.dirstate.add(f)
2517 repo.dirstate.add(f)
2517
2518
2518 normal = repo.dirstate.normallookup
2519 normal = repo.dirstate.normallookup
2519 if node == parent and p2 == nullid:
2520 if node == parent and p2 == nullid:
2520 normal = repo.dirstate.normal
2521 normal = repo.dirstate.normal
2521 for f in actions['undelete'][0]:
2522 for f in actions['undelete'][0]:
2522 checkout(f)
2523 checkout(f)
2523 normal(f)
2524 normal(f)
2524
2525
2525 copied = copies.pathcopies(repo[parent], ctx)
2526 copied = copies.pathcopies(repo[parent], ctx)
2526
2527
2527 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2528 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2528 if f in copied:
2529 if f in copied:
2529 repo.dirstate.copy(copied[f], f)
2530 repo.dirstate.copy(copied[f], f)
2530
2531
2531 def command(table):
2532 def command(table):
2532 """Returns a function object to be used as a decorator for making commands.
2533 """Returns a function object to be used as a decorator for making commands.
2533
2534
2534 This function receives a command table as its argument. The table should
2535 This function receives a command table as its argument. The table should
2535 be a dict.
2536 be a dict.
2536
2537
2537 The returned function can be used as a decorator for adding commands
2538 The returned function can be used as a decorator for adding commands
2538 to that command table. This function accepts multiple arguments to define
2539 to that command table. This function accepts multiple arguments to define
2539 a command.
2540 a command.
2540
2541
2541 The first argument is the command name.
2542 The first argument is the command name.
2542
2543
2543 The options argument is an iterable of tuples defining command arguments.
2544 The options argument is an iterable of tuples defining command arguments.
2544 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2545 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2545
2546
2546 The synopsis argument defines a short, one line summary of how to use the
2547 The synopsis argument defines a short, one line summary of how to use the
2547 command. This shows up in the help output.
2548 command. This shows up in the help output.
2548
2549
2549 The norepo argument defines whether the command does not require a
2550 The norepo argument defines whether the command does not require a
2550 local repository. Most commands operate against a repository, thus the
2551 local repository. Most commands operate against a repository, thus the
2551 default is False.
2552 default is False.
2552
2553
2553 The optionalrepo argument defines whether the command optionally requires
2554 The optionalrepo argument defines whether the command optionally requires
2554 a local repository.
2555 a local repository.
2555
2556
2556 The inferrepo argument defines whether to try to find a repository from the
2557 The inferrepo argument defines whether to try to find a repository from the
2557 command line arguments. If True, arguments will be examined for potential
2558 command line arguments. If True, arguments will be examined for potential
2558 repository locations. See ``findrepo()``. If a repository is found, it
2559 repository locations. See ``findrepo()``. If a repository is found, it
2559 will be used.
2560 will be used.
2560 """
2561 """
2561 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2562 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2562 inferrepo=False):
2563 inferrepo=False):
2563 def decorator(func):
2564 def decorator(func):
2564 if synopsis:
2565 if synopsis:
2565 table[name] = func, list(options), synopsis
2566 table[name] = func, list(options), synopsis
2566 else:
2567 else:
2567 table[name] = func, list(options)
2568 table[name] = func, list(options)
2568
2569
2569 if norepo:
2570 if norepo:
2570 # Avoid import cycle.
2571 # Avoid import cycle.
2571 import commands
2572 import commands
2572 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2573 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2573
2574
2574 if optionalrepo:
2575 if optionalrepo:
2575 import commands
2576 import commands
2576 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2577 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2577
2578
2578 if inferrepo:
2579 if inferrepo:
2579 import commands
2580 import commands
2580 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2581 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2581
2582
2582 return func
2583 return func
2583 return decorator
2584 return decorator
2584
2585
2585 return cmd
2586 return cmd
2586
2587
2587 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2588 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2588 # commands.outgoing. "missing" is "missing" of the result of
2589 # commands.outgoing. "missing" is "missing" of the result of
2589 # "findcommonoutgoing()"
2590 # "findcommonoutgoing()"
2590 outgoinghooks = util.hooks()
2591 outgoinghooks = util.hooks()
2591
2592
2592 # a list of (ui, repo) functions called by commands.summary
2593 # a list of (ui, repo) functions called by commands.summary
2593 summaryhooks = util.hooks()
2594 summaryhooks = util.hooks()
2594
2595
2595 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2596 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2596 #
2597 #
2597 # functions should return tuple of booleans below, if 'changes' is None:
2598 # functions should return tuple of booleans below, if 'changes' is None:
2598 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2599 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2599 #
2600 #
2600 # otherwise, 'changes' is a tuple of tuples below:
2601 # otherwise, 'changes' is a tuple of tuples below:
2601 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2602 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2602 # - (desturl, destbranch, destpeer, outgoing)
2603 # - (desturl, destbranch, destpeer, outgoing)
2603 summaryremotehooks = util.hooks()
2604 summaryremotehooks = util.hooks()
2604
2605
2605 # A list of state files kept by multistep operations like graft.
2606 # A list of state files kept by multistep operations like graft.
2606 # Since graft cannot be aborted, it is considered 'clearable' by update.
2607 # Since graft cannot be aborted, it is considered 'clearable' by update.
2607 # note: bisect is intentionally excluded
2608 # note: bisect is intentionally excluded
2608 # (state file, clearable, allowcommit, error, hint)
2609 # (state file, clearable, allowcommit, error, hint)
2609 unfinishedstates = [
2610 unfinishedstates = [
2610 ('graftstate', True, False, _('graft in progress'),
2611 ('graftstate', True, False, _('graft in progress'),
2611 _("use 'hg graft --continue' or 'hg update' to abort")),
2612 _("use 'hg graft --continue' or 'hg update' to abort")),
2612 ('updatestate', True, False, _('last update was interrupted'),
2613 ('updatestate', True, False, _('last update was interrupted'),
2613 _("use 'hg update' to get a consistent checkout"))
2614 _("use 'hg update' to get a consistent checkout"))
2614 ]
2615 ]
2615
2616
2616 def checkunfinished(repo, commit=False):
2617 def checkunfinished(repo, commit=False):
2617 '''Look for an unfinished multistep operation, like graft, and abort
2618 '''Look for an unfinished multistep operation, like graft, and abort
2618 if found. It's probably good to check this right before
2619 if found. It's probably good to check this right before
2619 bailifchanged().
2620 bailifchanged().
2620 '''
2621 '''
2621 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2622 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2622 if commit and allowcommit:
2623 if commit and allowcommit:
2623 continue
2624 continue
2624 if repo.vfs.exists(f):
2625 if repo.vfs.exists(f):
2625 raise util.Abort(msg, hint=hint)
2626 raise util.Abort(msg, hint=hint)
2626
2627
2627 def clearunfinished(repo):
2628 def clearunfinished(repo):
2628 '''Check for unfinished operations (as above), and clear the ones
2629 '''Check for unfinished operations (as above), and clear the ones
2629 that are clearable.
2630 that are clearable.
2630 '''
2631 '''
2631 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2632 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2632 if not clearable and repo.vfs.exists(f):
2633 if not clearable and repo.vfs.exists(f):
2633 raise util.Abort(msg, hint=hint)
2634 raise util.Abort(msg, hint=hint)
2634 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2635 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2635 if clearable and repo.vfs.exists(f):
2636 if clearable and repo.vfs.exists(f):
2636 util.unlink(repo.join(f))
2637 util.unlink(repo.join(f))
General Comments 0
You need to be logged in to leave comments. Login now