##// END OF EJS Templates
revert: handle unknown files through status...
Pierre-Yves David -
r22236:3c24fb96 default
parent child Browse files
Show More
@@ -1,2701 +1,2706
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(editform='import.normal', **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 editor = getcommiteditor(editform='import.bypass')
691 memctx = context.makememctx(repo, (p1.node(), p2.node()),
691 memctx = context.makememctx(repo, (p1.node(), p2.node()),
692 message,
692 message,
693 opts.get('user') or user,
693 opts.get('user') or user,
694 opts.get('date') or date,
694 opts.get('date') or date,
695 branch, files, store,
695 branch, files, store,
696 editor=editor)
696 editor=editor)
697 n = memctx.commit()
697 n = memctx.commit()
698 finally:
698 finally:
699 store.close()
699 store.close()
700 if opts.get('exact') and hex(n) != nodeid:
700 if opts.get('exact') and hex(n) != nodeid:
701 raise util.Abort(_('patch is damaged or loses information'))
701 raise util.Abort(_('patch is damaged or loses information'))
702 if n:
702 if n:
703 # i18n: refers to a short changeset id
703 # i18n: refers to a short changeset id
704 msg = _('created %s') % short(n)
704 msg = _('created %s') % short(n)
705 return (msg, n, rejects)
705 return (msg, n, rejects)
706 finally:
706 finally:
707 os.unlink(tmpname)
707 os.unlink(tmpname)
708
708
709 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,
710 opts=None):
710 opts=None):
711 '''export changesets as hg patches.'''
711 '''export changesets as hg patches.'''
712
712
713 total = len(revs)
713 total = len(revs)
714 revwidth = max([len(str(rev)) for rev in revs])
714 revwidth = max([len(str(rev)) for rev in revs])
715 filemode = {}
715 filemode = {}
716
716
717 def single(rev, seqno, fp):
717 def single(rev, seqno, fp):
718 ctx = repo[rev]
718 ctx = repo[rev]
719 node = ctx.node()
719 node = ctx.node()
720 parents = [p.node() for p in ctx.parents() if p]
720 parents = [p.node() for p in ctx.parents() if p]
721 branch = ctx.branch()
721 branch = ctx.branch()
722 if switch_parent:
722 if switch_parent:
723 parents.reverse()
723 parents.reverse()
724 prev = (parents and parents[0]) or nullid
724 prev = (parents and parents[0]) or nullid
725
725
726 shouldclose = False
726 shouldclose = False
727 if not fp and len(template) > 0:
727 if not fp and len(template) > 0:
728 desc_lines = ctx.description().rstrip().split('\n')
728 desc_lines = ctx.description().rstrip().split('\n')
729 desc = desc_lines[0] #Commit always has a first line.
729 desc = desc_lines[0] #Commit always has a first line.
730 fp = makefileobj(repo, template, node, desc=desc, total=total,
730 fp = makefileobj(repo, template, node, desc=desc, total=total,
731 seqno=seqno, revwidth=revwidth, mode='wb',
731 seqno=seqno, revwidth=revwidth, mode='wb',
732 modemap=filemode)
732 modemap=filemode)
733 if fp != template:
733 if fp != template:
734 shouldclose = True
734 shouldclose = True
735 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
735 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
736 repo.ui.note("%s\n" % fp.name)
736 repo.ui.note("%s\n" % fp.name)
737
737
738 if not fp:
738 if not fp:
739 write = repo.ui.write
739 write = repo.ui.write
740 else:
740 else:
741 def write(s, **kw):
741 def write(s, **kw):
742 fp.write(s)
742 fp.write(s)
743
743
744
744
745 write("# HG changeset patch\n")
745 write("# HG changeset patch\n")
746 write("# User %s\n" % ctx.user())
746 write("# User %s\n" % ctx.user())
747 write("# Date %d %d\n" % ctx.date())
747 write("# Date %d %d\n" % ctx.date())
748 write("# %s\n" % util.datestr(ctx.date()))
748 write("# %s\n" % util.datestr(ctx.date()))
749 if branch and branch != 'default':
749 if branch and branch != 'default':
750 write("# Branch %s\n" % branch)
750 write("# Branch %s\n" % branch)
751 write("# Node ID %s\n" % hex(node))
751 write("# Node ID %s\n" % hex(node))
752 write("# Parent %s\n" % hex(prev))
752 write("# Parent %s\n" % hex(prev))
753 if len(parents) > 1:
753 if len(parents) > 1:
754 write("# Parent %s\n" % hex(parents[1]))
754 write("# Parent %s\n" % hex(parents[1]))
755 write(ctx.description().rstrip())
755 write(ctx.description().rstrip())
756 write("\n\n")
756 write("\n\n")
757
757
758 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
758 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
759 write(chunk, label=label)
759 write(chunk, label=label)
760
760
761 if shouldclose:
761 if shouldclose:
762 fp.close()
762 fp.close()
763
763
764 for seqno, rev in enumerate(revs):
764 for seqno, rev in enumerate(revs):
765 single(rev, seqno + 1, fp)
765 single(rev, seqno + 1, fp)
766
766
767 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
767 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
768 changes=None, stat=False, fp=None, prefix='',
768 changes=None, stat=False, fp=None, prefix='',
769 listsubrepos=False):
769 listsubrepos=False):
770 '''show diff or diffstat.'''
770 '''show diff or diffstat.'''
771 if fp is None:
771 if fp is None:
772 write = ui.write
772 write = ui.write
773 else:
773 else:
774 def write(s, **kw):
774 def write(s, **kw):
775 fp.write(s)
775 fp.write(s)
776
776
777 if stat:
777 if stat:
778 diffopts = diffopts.copy(context=0)
778 diffopts = diffopts.copy(context=0)
779 width = 80
779 width = 80
780 if not ui.plain():
780 if not ui.plain():
781 width = ui.termwidth()
781 width = ui.termwidth()
782 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
782 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
783 prefix=prefix)
783 prefix=prefix)
784 for chunk, label in patch.diffstatui(util.iterlines(chunks),
784 for chunk, label in patch.diffstatui(util.iterlines(chunks),
785 width=width,
785 width=width,
786 git=diffopts.git):
786 git=diffopts.git):
787 write(chunk, label=label)
787 write(chunk, label=label)
788 else:
788 else:
789 for chunk, label in patch.diffui(repo, node1, node2, match,
789 for chunk, label in patch.diffui(repo, node1, node2, match,
790 changes, diffopts, prefix=prefix):
790 changes, diffopts, prefix=prefix):
791 write(chunk, label=label)
791 write(chunk, label=label)
792
792
793 if listsubrepos:
793 if listsubrepos:
794 ctx1 = repo[node1]
794 ctx1 = repo[node1]
795 ctx2 = repo[node2]
795 ctx2 = repo[node2]
796 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
796 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
797 tempnode2 = node2
797 tempnode2 = node2
798 try:
798 try:
799 if node2 is not None:
799 if node2 is not None:
800 tempnode2 = ctx2.substate[subpath][1]
800 tempnode2 = ctx2.substate[subpath][1]
801 except KeyError:
801 except KeyError:
802 # A subrepo that existed in node1 was deleted between node1 and
802 # A subrepo that existed in node1 was deleted between node1 and
803 # node2 (inclusive). Thus, ctx2's substate won't contain that
803 # node2 (inclusive). Thus, ctx2's substate won't contain that
804 # subpath. The best we can do is to ignore it.
804 # subpath. The best we can do is to ignore it.
805 tempnode2 = None
805 tempnode2 = None
806 submatch = matchmod.narrowmatcher(subpath, match)
806 submatch = matchmod.narrowmatcher(subpath, match)
807 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
807 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
808 stat=stat, fp=fp, prefix=prefix)
808 stat=stat, fp=fp, prefix=prefix)
809
809
810 class changeset_printer(object):
810 class changeset_printer(object):
811 '''show changeset information when templating not requested.'''
811 '''show changeset information when templating not requested.'''
812
812
813 def __init__(self, ui, repo, patch, diffopts, buffered):
813 def __init__(self, ui, repo, patch, diffopts, buffered):
814 self.ui = ui
814 self.ui = ui
815 self.repo = repo
815 self.repo = repo
816 self.buffered = buffered
816 self.buffered = buffered
817 self.patch = patch
817 self.patch = patch
818 self.diffopts = diffopts
818 self.diffopts = diffopts
819 self.header = {}
819 self.header = {}
820 self.hunk = {}
820 self.hunk = {}
821 self.lastheader = None
821 self.lastheader = None
822 self.footer = None
822 self.footer = None
823
823
824 def flush(self, rev):
824 def flush(self, rev):
825 if rev in self.header:
825 if rev in self.header:
826 h = self.header[rev]
826 h = self.header[rev]
827 if h != self.lastheader:
827 if h != self.lastheader:
828 self.lastheader = h
828 self.lastheader = h
829 self.ui.write(h)
829 self.ui.write(h)
830 del self.header[rev]
830 del self.header[rev]
831 if rev in self.hunk:
831 if rev in self.hunk:
832 self.ui.write(self.hunk[rev])
832 self.ui.write(self.hunk[rev])
833 del self.hunk[rev]
833 del self.hunk[rev]
834 return 1
834 return 1
835 return 0
835 return 0
836
836
837 def close(self):
837 def close(self):
838 if self.footer:
838 if self.footer:
839 self.ui.write(self.footer)
839 self.ui.write(self.footer)
840
840
841 def show(self, ctx, copies=None, matchfn=None, **props):
841 def show(self, ctx, copies=None, matchfn=None, **props):
842 if self.buffered:
842 if self.buffered:
843 self.ui.pushbuffer()
843 self.ui.pushbuffer()
844 self._show(ctx, copies, matchfn, props)
844 self._show(ctx, copies, matchfn, props)
845 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
845 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
846 else:
846 else:
847 self._show(ctx, copies, matchfn, props)
847 self._show(ctx, copies, matchfn, props)
848
848
849 def _show(self, ctx, copies, matchfn, props):
849 def _show(self, ctx, copies, matchfn, props):
850 '''show a single changeset or file revision'''
850 '''show a single changeset or file revision'''
851 changenode = ctx.node()
851 changenode = ctx.node()
852 rev = ctx.rev()
852 rev = ctx.rev()
853
853
854 if self.ui.quiet:
854 if self.ui.quiet:
855 self.ui.write("%d:%s\n" % (rev, short(changenode)),
855 self.ui.write("%d:%s\n" % (rev, short(changenode)),
856 label='log.node')
856 label='log.node')
857 return
857 return
858
858
859 log = self.repo.changelog
859 log = self.repo.changelog
860 date = util.datestr(ctx.date())
860 date = util.datestr(ctx.date())
861
861
862 hexfunc = self.ui.debugflag and hex or short
862 hexfunc = self.ui.debugflag and hex or short
863
863
864 parents = [(p, hexfunc(log.node(p)))
864 parents = [(p, hexfunc(log.node(p)))
865 for p in self._meaningful_parentrevs(log, rev)]
865 for p in self._meaningful_parentrevs(log, rev)]
866
866
867 # i18n: column positioning for "hg log"
867 # i18n: column positioning for "hg log"
868 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
868 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
869 label='log.changeset changeset.%s' % ctx.phasestr())
869 label='log.changeset changeset.%s' % ctx.phasestr())
870
870
871 branch = ctx.branch()
871 branch = ctx.branch()
872 # don't show the default branch name
872 # don't show the default branch name
873 if branch != 'default':
873 if branch != 'default':
874 # i18n: column positioning for "hg log"
874 # i18n: column positioning for "hg log"
875 self.ui.write(_("branch: %s\n") % branch,
875 self.ui.write(_("branch: %s\n") % branch,
876 label='log.branch')
876 label='log.branch')
877 for bookmark in self.repo.nodebookmarks(changenode):
877 for bookmark in self.repo.nodebookmarks(changenode):
878 # i18n: column positioning for "hg log"
878 # i18n: column positioning for "hg log"
879 self.ui.write(_("bookmark: %s\n") % bookmark,
879 self.ui.write(_("bookmark: %s\n") % bookmark,
880 label='log.bookmark')
880 label='log.bookmark')
881 for tag in self.repo.nodetags(changenode):
881 for tag in self.repo.nodetags(changenode):
882 # i18n: column positioning for "hg log"
882 # i18n: column positioning for "hg log"
883 self.ui.write(_("tag: %s\n") % tag,
883 self.ui.write(_("tag: %s\n") % tag,
884 label='log.tag')
884 label='log.tag')
885 if self.ui.debugflag and ctx.phase():
885 if self.ui.debugflag and ctx.phase():
886 # i18n: column positioning for "hg log"
886 # i18n: column positioning for "hg log"
887 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
887 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
888 label='log.phase')
888 label='log.phase')
889 for parent in parents:
889 for parent in parents:
890 # i18n: column positioning for "hg log"
890 # i18n: column positioning for "hg log"
891 self.ui.write(_("parent: %d:%s\n") % parent,
891 self.ui.write(_("parent: %d:%s\n") % parent,
892 label='log.parent changeset.%s' % ctx.phasestr())
892 label='log.parent changeset.%s' % ctx.phasestr())
893
893
894 if self.ui.debugflag:
894 if self.ui.debugflag:
895 mnode = ctx.manifestnode()
895 mnode = ctx.manifestnode()
896 # i18n: column positioning for "hg log"
896 # i18n: column positioning for "hg log"
897 self.ui.write(_("manifest: %d:%s\n") %
897 self.ui.write(_("manifest: %d:%s\n") %
898 (self.repo.manifest.rev(mnode), hex(mnode)),
898 (self.repo.manifest.rev(mnode), hex(mnode)),
899 label='ui.debug log.manifest')
899 label='ui.debug log.manifest')
900 # i18n: column positioning for "hg log"
900 # i18n: column positioning for "hg log"
901 self.ui.write(_("user: %s\n") % ctx.user(),
901 self.ui.write(_("user: %s\n") % ctx.user(),
902 label='log.user')
902 label='log.user')
903 # i18n: column positioning for "hg log"
903 # i18n: column positioning for "hg log"
904 self.ui.write(_("date: %s\n") % date,
904 self.ui.write(_("date: %s\n") % date,
905 label='log.date')
905 label='log.date')
906
906
907 if self.ui.debugflag:
907 if self.ui.debugflag:
908 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
908 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
909 for key, value in zip([# i18n: column positioning for "hg log"
909 for key, value in zip([# i18n: column positioning for "hg log"
910 _("files:"),
910 _("files:"),
911 # i18n: column positioning for "hg log"
911 # i18n: column positioning for "hg log"
912 _("files+:"),
912 _("files+:"),
913 # i18n: column positioning for "hg log"
913 # i18n: column positioning for "hg log"
914 _("files-:")], files):
914 _("files-:")], files):
915 if value:
915 if value:
916 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
916 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
917 label='ui.debug log.files')
917 label='ui.debug log.files')
918 elif ctx.files() and self.ui.verbose:
918 elif ctx.files() and self.ui.verbose:
919 # i18n: column positioning for "hg log"
919 # i18n: column positioning for "hg log"
920 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
920 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
921 label='ui.note log.files')
921 label='ui.note log.files')
922 if copies and self.ui.verbose:
922 if copies and self.ui.verbose:
923 copies = ['%s (%s)' % c for c in copies]
923 copies = ['%s (%s)' % c for c in copies]
924 # i18n: column positioning for "hg log"
924 # i18n: column positioning for "hg log"
925 self.ui.write(_("copies: %s\n") % ' '.join(copies),
925 self.ui.write(_("copies: %s\n") % ' '.join(copies),
926 label='ui.note log.copies')
926 label='ui.note log.copies')
927
927
928 extra = ctx.extra()
928 extra = ctx.extra()
929 if extra and self.ui.debugflag:
929 if extra and self.ui.debugflag:
930 for key, value in sorted(extra.items()):
930 for key, value in sorted(extra.items()):
931 # i18n: column positioning for "hg log"
931 # i18n: column positioning for "hg log"
932 self.ui.write(_("extra: %s=%s\n")
932 self.ui.write(_("extra: %s=%s\n")
933 % (key, value.encode('string_escape')),
933 % (key, value.encode('string_escape')),
934 label='ui.debug log.extra')
934 label='ui.debug log.extra')
935
935
936 description = ctx.description().strip()
936 description = ctx.description().strip()
937 if description:
937 if description:
938 if self.ui.verbose:
938 if self.ui.verbose:
939 self.ui.write(_("description:\n"),
939 self.ui.write(_("description:\n"),
940 label='ui.note log.description')
940 label='ui.note log.description')
941 self.ui.write(description,
941 self.ui.write(description,
942 label='ui.note log.description')
942 label='ui.note log.description')
943 self.ui.write("\n\n")
943 self.ui.write("\n\n")
944 else:
944 else:
945 # i18n: column positioning for "hg log"
945 # i18n: column positioning for "hg log"
946 self.ui.write(_("summary: %s\n") %
946 self.ui.write(_("summary: %s\n") %
947 description.splitlines()[0],
947 description.splitlines()[0],
948 label='log.summary')
948 label='log.summary')
949 self.ui.write("\n")
949 self.ui.write("\n")
950
950
951 self.showpatch(changenode, matchfn)
951 self.showpatch(changenode, matchfn)
952
952
953 def showpatch(self, node, matchfn):
953 def showpatch(self, node, matchfn):
954 if not matchfn:
954 if not matchfn:
955 matchfn = self.patch
955 matchfn = self.patch
956 if matchfn:
956 if matchfn:
957 stat = self.diffopts.get('stat')
957 stat = self.diffopts.get('stat')
958 diff = self.diffopts.get('patch')
958 diff = self.diffopts.get('patch')
959 diffopts = patch.diffopts(self.ui, self.diffopts)
959 diffopts = patch.diffopts(self.ui, self.diffopts)
960 prev = self.repo.changelog.parents(node)[0]
960 prev = self.repo.changelog.parents(node)[0]
961 if stat:
961 if stat:
962 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
962 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
963 match=matchfn, stat=True)
963 match=matchfn, stat=True)
964 if diff:
964 if diff:
965 if stat:
965 if stat:
966 self.ui.write("\n")
966 self.ui.write("\n")
967 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
967 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
968 match=matchfn, stat=False)
968 match=matchfn, stat=False)
969 self.ui.write("\n")
969 self.ui.write("\n")
970
970
971 def _meaningful_parentrevs(self, log, rev):
971 def _meaningful_parentrevs(self, log, rev):
972 """Return list of meaningful (or all if debug) parentrevs for rev.
972 """Return list of meaningful (or all if debug) parentrevs for rev.
973
973
974 For merges (two non-nullrev revisions) both parents are meaningful.
974 For merges (two non-nullrev revisions) both parents are meaningful.
975 Otherwise the first parent revision is considered meaningful if it
975 Otherwise the first parent revision is considered meaningful if it
976 is not the preceding revision.
976 is not the preceding revision.
977 """
977 """
978 parents = log.parentrevs(rev)
978 parents = log.parentrevs(rev)
979 if not self.ui.debugflag and parents[1] == nullrev:
979 if not self.ui.debugflag and parents[1] == nullrev:
980 if parents[0] >= rev - 1:
980 if parents[0] >= rev - 1:
981 parents = []
981 parents = []
982 else:
982 else:
983 parents = [parents[0]]
983 parents = [parents[0]]
984 return parents
984 return parents
985
985
986
986
987 class changeset_templater(changeset_printer):
987 class changeset_templater(changeset_printer):
988 '''format changeset information.'''
988 '''format changeset information.'''
989
989
990 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
990 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
991 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
991 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
992 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])
993 defaulttempl = {
993 defaulttempl = {
994 'parent': '{rev}:{node|formatnode} ',
994 'parent': '{rev}:{node|formatnode} ',
995 'manifest': '{rev}:{node|formatnode}',
995 'manifest': '{rev}:{node|formatnode}',
996 'file_copy': '{name} ({source})',
996 'file_copy': '{name} ({source})',
997 'extra': '{key}={value|stringescape}'
997 'extra': '{key}={value|stringescape}'
998 }
998 }
999 # filecopy is preserved for compatibility reasons
999 # filecopy is preserved for compatibility reasons
1000 defaulttempl['filecopy'] = defaulttempl['file_copy']
1000 defaulttempl['filecopy'] = defaulttempl['file_copy']
1001 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1001 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1002 cache=defaulttempl)
1002 cache=defaulttempl)
1003 if tmpl:
1003 if tmpl:
1004 self.t.cache['changeset'] = tmpl
1004 self.t.cache['changeset'] = tmpl
1005
1005
1006 self.cache = {}
1006 self.cache = {}
1007
1007
1008 def _meaningful_parentrevs(self, ctx):
1008 def _meaningful_parentrevs(self, ctx):
1009 """Return list of meaningful (or all if debug) parentrevs for rev.
1009 """Return list of meaningful (or all if debug) parentrevs for rev.
1010 """
1010 """
1011 parents = ctx.parents()
1011 parents = ctx.parents()
1012 if len(parents) > 1:
1012 if len(parents) > 1:
1013 return parents
1013 return parents
1014 if self.ui.debugflag:
1014 if self.ui.debugflag:
1015 return [parents[0], self.repo['null']]
1015 return [parents[0], self.repo['null']]
1016 if parents[0].rev() >= ctx.rev() - 1:
1016 if parents[0].rev() >= ctx.rev() - 1:
1017 return []
1017 return []
1018 return parents
1018 return parents
1019
1019
1020 def _show(self, ctx, copies, matchfn, props):
1020 def _show(self, ctx, copies, matchfn, props):
1021 '''show a single changeset or file revision'''
1021 '''show a single changeset or file revision'''
1022
1022
1023 showlist = templatekw.showlist
1023 showlist = templatekw.showlist
1024
1024
1025 # showparents() behaviour depends on ui trace level which
1025 # showparents() behaviour depends on ui trace level which
1026 # causes unexpected behaviours at templating level and makes
1026 # causes unexpected behaviours at templating level and makes
1027 # it harder to extract it in a standalone function. Its
1027 # it harder to extract it in a standalone function. Its
1028 # behaviour cannot be changed so leave it here for now.
1028 # behaviour cannot be changed so leave it here for now.
1029 def showparents(**args):
1029 def showparents(**args):
1030 ctx = args['ctx']
1030 ctx = args['ctx']
1031 parents = [[('rev', p.rev()), ('node', p.hex())]
1031 parents = [[('rev', p.rev()), ('node', p.hex())]
1032 for p in self._meaningful_parentrevs(ctx)]
1032 for p in self._meaningful_parentrevs(ctx)]
1033 return showlist('parent', parents, **args)
1033 return showlist('parent', parents, **args)
1034
1034
1035 props = props.copy()
1035 props = props.copy()
1036 props.update(templatekw.keywords)
1036 props.update(templatekw.keywords)
1037 props['parents'] = showparents
1037 props['parents'] = showparents
1038 props['templ'] = self.t
1038 props['templ'] = self.t
1039 props['ctx'] = ctx
1039 props['ctx'] = ctx
1040 props['repo'] = self.repo
1040 props['repo'] = self.repo
1041 props['revcache'] = {'copies': copies}
1041 props['revcache'] = {'copies': copies}
1042 props['cache'] = self.cache
1042 props['cache'] = self.cache
1043
1043
1044 # find correct templates for current mode
1044 # find correct templates for current mode
1045
1045
1046 tmplmodes = [
1046 tmplmodes = [
1047 (True, None),
1047 (True, None),
1048 (self.ui.verbose, 'verbose'),
1048 (self.ui.verbose, 'verbose'),
1049 (self.ui.quiet, 'quiet'),
1049 (self.ui.quiet, 'quiet'),
1050 (self.ui.debugflag, 'debug'),
1050 (self.ui.debugflag, 'debug'),
1051 ]
1051 ]
1052
1052
1053 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1053 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1054 for mode, postfix in tmplmodes:
1054 for mode, postfix in tmplmodes:
1055 for type in types:
1055 for type in types:
1056 cur = postfix and ('%s_%s' % (type, postfix)) or type
1056 cur = postfix and ('%s_%s' % (type, postfix)) or type
1057 if mode and cur in self.t:
1057 if mode and cur in self.t:
1058 types[type] = cur
1058 types[type] = cur
1059
1059
1060 try:
1060 try:
1061
1061
1062 # write header
1062 # write header
1063 if types['header']:
1063 if types['header']:
1064 h = templater.stringify(self.t(types['header'], **props))
1064 h = templater.stringify(self.t(types['header'], **props))
1065 if self.buffered:
1065 if self.buffered:
1066 self.header[ctx.rev()] = h
1066 self.header[ctx.rev()] = h
1067 else:
1067 else:
1068 if self.lastheader != h:
1068 if self.lastheader != h:
1069 self.lastheader = h
1069 self.lastheader = h
1070 self.ui.write(h)
1070 self.ui.write(h)
1071
1071
1072 # write changeset metadata, then patch if requested
1072 # write changeset metadata, then patch if requested
1073 key = types['changeset']
1073 key = types['changeset']
1074 self.ui.write(templater.stringify(self.t(key, **props)))
1074 self.ui.write(templater.stringify(self.t(key, **props)))
1075 self.showpatch(ctx.node(), matchfn)
1075 self.showpatch(ctx.node(), matchfn)
1076
1076
1077 if types['footer']:
1077 if types['footer']:
1078 if not self.footer:
1078 if not self.footer:
1079 self.footer = templater.stringify(self.t(types['footer'],
1079 self.footer = templater.stringify(self.t(types['footer'],
1080 **props))
1080 **props))
1081
1081
1082 except KeyError, inst:
1082 except KeyError, inst:
1083 msg = _("%s: no key named '%s'")
1083 msg = _("%s: no key named '%s'")
1084 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1084 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1085 except SyntaxError, inst:
1085 except SyntaxError, inst:
1086 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1086 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1087
1087
1088 def gettemplate(ui, tmpl, style):
1088 def gettemplate(ui, tmpl, style):
1089 """
1089 """
1090 Find the template matching the given template spec or style.
1090 Find the template matching the given template spec or style.
1091 """
1091 """
1092
1092
1093 # ui settings
1093 # ui settings
1094 if not tmpl and not style:
1094 if not tmpl and not style:
1095 tmpl = ui.config('ui', 'logtemplate')
1095 tmpl = ui.config('ui', 'logtemplate')
1096 if tmpl:
1096 if tmpl:
1097 try:
1097 try:
1098 tmpl = templater.parsestring(tmpl)
1098 tmpl = templater.parsestring(tmpl)
1099 except SyntaxError:
1099 except SyntaxError:
1100 tmpl = templater.parsestring(tmpl, quoted=False)
1100 tmpl = templater.parsestring(tmpl, quoted=False)
1101 return tmpl, None
1101 return tmpl, None
1102 else:
1102 else:
1103 style = util.expandpath(ui.config('ui', 'style', ''))
1103 style = util.expandpath(ui.config('ui', 'style', ''))
1104
1104
1105 if style:
1105 if style:
1106 mapfile = style
1106 mapfile = style
1107 if not os.path.split(mapfile)[0]:
1107 if not os.path.split(mapfile)[0]:
1108 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1108 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1109 or templater.templatepath(mapfile))
1109 or templater.templatepath(mapfile))
1110 if mapname:
1110 if mapname:
1111 mapfile = mapname
1111 mapfile = mapname
1112 return None, mapfile
1112 return None, mapfile
1113
1113
1114 if not tmpl:
1114 if not tmpl:
1115 return None, None
1115 return None, None
1116
1116
1117 # looks like a literal template?
1117 # looks like a literal template?
1118 if '{' in tmpl:
1118 if '{' in tmpl:
1119 return tmpl, None
1119 return tmpl, None
1120
1120
1121 # perhaps a stock style?
1121 # perhaps a stock style?
1122 if not os.path.split(tmpl)[0]:
1122 if not os.path.split(tmpl)[0]:
1123 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1123 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1124 or templater.templatepath(tmpl))
1124 or templater.templatepath(tmpl))
1125 if mapname and os.path.isfile(mapname):
1125 if mapname and os.path.isfile(mapname):
1126 return None, mapname
1126 return None, mapname
1127
1127
1128 # perhaps it's a reference to [templates]
1128 # perhaps it's a reference to [templates]
1129 t = ui.config('templates', tmpl)
1129 t = ui.config('templates', tmpl)
1130 if t:
1130 if t:
1131 try:
1131 try:
1132 tmpl = templater.parsestring(t)
1132 tmpl = templater.parsestring(t)
1133 except SyntaxError:
1133 except SyntaxError:
1134 tmpl = templater.parsestring(t, quoted=False)
1134 tmpl = templater.parsestring(t, quoted=False)
1135 return tmpl, None
1135 return tmpl, None
1136
1136
1137 if tmpl == 'list':
1137 if tmpl == 'list':
1138 ui.write(_("available styles: %s\n") % templater.stylelist())
1138 ui.write(_("available styles: %s\n") % templater.stylelist())
1139 raise util.Abort(_("specify a template"))
1139 raise util.Abort(_("specify a template"))
1140
1140
1141 # perhaps it's a path to a map or a template
1141 # perhaps it's a path to a map or a template
1142 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1142 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1143 # is it a mapfile for a style?
1143 # is it a mapfile for a style?
1144 if os.path.basename(tmpl).startswith("map-"):
1144 if os.path.basename(tmpl).startswith("map-"):
1145 return None, os.path.realpath(tmpl)
1145 return None, os.path.realpath(tmpl)
1146 tmpl = open(tmpl).read()
1146 tmpl = open(tmpl).read()
1147 return tmpl, None
1147 return tmpl, None
1148
1148
1149 # constant string?
1149 # constant string?
1150 return tmpl, None
1150 return tmpl, None
1151
1151
1152 def show_changeset(ui, repo, opts, buffered=False):
1152 def show_changeset(ui, repo, opts, buffered=False):
1153 """show one changeset using template or regular display.
1153 """show one changeset using template or regular display.
1154
1154
1155 Display format will be the first non-empty hit of:
1155 Display format will be the first non-empty hit of:
1156 1. option 'template'
1156 1. option 'template'
1157 2. option 'style'
1157 2. option 'style'
1158 3. [ui] setting 'logtemplate'
1158 3. [ui] setting 'logtemplate'
1159 4. [ui] setting 'style'
1159 4. [ui] setting 'style'
1160 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,
1161 regular display via changeset_printer() is done.
1161 regular display via changeset_printer() is done.
1162 """
1162 """
1163 # options
1163 # options
1164 patch = None
1164 patch = None
1165 if opts.get('patch') or opts.get('stat'):
1165 if opts.get('patch') or opts.get('stat'):
1166 patch = scmutil.matchall(repo)
1166 patch = scmutil.matchall(repo)
1167
1167
1168 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1168 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1169
1169
1170 if not tmpl and not mapfile:
1170 if not tmpl and not mapfile:
1171 return changeset_printer(ui, repo, patch, opts, buffered)
1171 return changeset_printer(ui, repo, patch, opts, buffered)
1172
1172
1173 try:
1173 try:
1174 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1174 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1175 except SyntaxError, inst:
1175 except SyntaxError, inst:
1176 raise util.Abort(inst.args[0])
1176 raise util.Abort(inst.args[0])
1177 return t
1177 return t
1178
1178
1179 def showmarker(ui, marker):
1179 def showmarker(ui, marker):
1180 """utility function to display obsolescence marker in a readable way
1180 """utility function to display obsolescence marker in a readable way
1181
1181
1182 To be used by debug function."""
1182 To be used by debug function."""
1183 ui.write(hex(marker.precnode()))
1183 ui.write(hex(marker.precnode()))
1184 for repl in marker.succnodes():
1184 for repl in marker.succnodes():
1185 ui.write(' ')
1185 ui.write(' ')
1186 ui.write(hex(repl))
1186 ui.write(hex(repl))
1187 ui.write(' %X ' % marker.flags())
1187 ui.write(' %X ' % marker.flags())
1188 ui.write('(%s) ' % util.datestr(marker.date()))
1188 ui.write('(%s) ' % util.datestr(marker.date()))
1189 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1189 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1190 sorted(marker.metadata().items())
1190 sorted(marker.metadata().items())
1191 if t[0] != 'date')))
1191 if t[0] != 'date')))
1192 ui.write('\n')
1192 ui.write('\n')
1193
1193
1194 def finddate(ui, repo, date):
1194 def finddate(ui, repo, date):
1195 """Find the tipmost changeset that matches the given date spec"""
1195 """Find the tipmost changeset that matches the given date spec"""
1196
1196
1197 df = util.matchdate(date)
1197 df = util.matchdate(date)
1198 m = scmutil.matchall(repo)
1198 m = scmutil.matchall(repo)
1199 results = {}
1199 results = {}
1200
1200
1201 def prep(ctx, fns):
1201 def prep(ctx, fns):
1202 d = ctx.date()
1202 d = ctx.date()
1203 if df(d[0]):
1203 if df(d[0]):
1204 results[ctx.rev()] = d
1204 results[ctx.rev()] = d
1205
1205
1206 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1206 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1207 rev = ctx.rev()
1207 rev = ctx.rev()
1208 if rev in results:
1208 if rev in results:
1209 ui.status(_("found revision %s from %s\n") %
1209 ui.status(_("found revision %s from %s\n") %
1210 (rev, util.datestr(results[rev])))
1210 (rev, util.datestr(results[rev])))
1211 return str(rev)
1211 return str(rev)
1212
1212
1213 raise util.Abort(_("revision matching date not found"))
1213 raise util.Abort(_("revision matching date not found"))
1214
1214
1215 def increasingwindows(windowsize=8, sizelimit=512):
1215 def increasingwindows(windowsize=8, sizelimit=512):
1216 while True:
1216 while True:
1217 yield windowsize
1217 yield windowsize
1218 if windowsize < sizelimit:
1218 if windowsize < sizelimit:
1219 windowsize *= 2
1219 windowsize *= 2
1220
1220
1221 class FileWalkError(Exception):
1221 class FileWalkError(Exception):
1222 pass
1222 pass
1223
1223
1224 def walkfilerevs(repo, match, follow, revs, fncache):
1224 def walkfilerevs(repo, match, follow, revs, fncache):
1225 '''Walks the file history for the matched files.
1225 '''Walks the file history for the matched files.
1226
1226
1227 Returns the changeset revs that are involved in the file history.
1227 Returns the changeset revs that are involved in the file history.
1228
1228
1229 Throws FileWalkError if the file history can't be walked using
1229 Throws FileWalkError if the file history can't be walked using
1230 filelogs alone.
1230 filelogs alone.
1231 '''
1231 '''
1232 wanted = set()
1232 wanted = set()
1233 copies = []
1233 copies = []
1234 minrev, maxrev = min(revs), max(revs)
1234 minrev, maxrev = min(revs), max(revs)
1235 def filerevgen(filelog, last):
1235 def filerevgen(filelog, last):
1236 """
1236 """
1237 Only files, no patterns. Check the history of each file.
1237 Only files, no patterns. Check the history of each file.
1238
1238
1239 Examines filelog entries within minrev, maxrev linkrev range
1239 Examines filelog entries within minrev, maxrev linkrev range
1240 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1240 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1241 tuples in backwards order
1241 tuples in backwards order
1242 """
1242 """
1243 cl_count = len(repo)
1243 cl_count = len(repo)
1244 revs = []
1244 revs = []
1245 for j in xrange(0, last + 1):
1245 for j in xrange(0, last + 1):
1246 linkrev = filelog.linkrev(j)
1246 linkrev = filelog.linkrev(j)
1247 if linkrev < minrev:
1247 if linkrev < minrev:
1248 continue
1248 continue
1249 # only yield rev for which we have the changelog, it can
1249 # only yield rev for which we have the changelog, it can
1250 # happen while doing "hg log" during a pull or commit
1250 # happen while doing "hg log" during a pull or commit
1251 if linkrev >= cl_count:
1251 if linkrev >= cl_count:
1252 break
1252 break
1253
1253
1254 parentlinkrevs = []
1254 parentlinkrevs = []
1255 for p in filelog.parentrevs(j):
1255 for p in filelog.parentrevs(j):
1256 if p != nullrev:
1256 if p != nullrev:
1257 parentlinkrevs.append(filelog.linkrev(p))
1257 parentlinkrevs.append(filelog.linkrev(p))
1258 n = filelog.node(j)
1258 n = filelog.node(j)
1259 revs.append((linkrev, parentlinkrevs,
1259 revs.append((linkrev, parentlinkrevs,
1260 follow and filelog.renamed(n)))
1260 follow and filelog.renamed(n)))
1261
1261
1262 return reversed(revs)
1262 return reversed(revs)
1263 def iterfiles():
1263 def iterfiles():
1264 pctx = repo['.']
1264 pctx = repo['.']
1265 for filename in match.files():
1265 for filename in match.files():
1266 if follow:
1266 if follow:
1267 if filename not in pctx:
1267 if filename not in pctx:
1268 raise util.Abort(_('cannot follow file not in parent '
1268 raise util.Abort(_('cannot follow file not in parent '
1269 'revision: "%s"') % filename)
1269 'revision: "%s"') % filename)
1270 yield filename, pctx[filename].filenode()
1270 yield filename, pctx[filename].filenode()
1271 else:
1271 else:
1272 yield filename, None
1272 yield filename, None
1273 for filename_node in copies:
1273 for filename_node in copies:
1274 yield filename_node
1274 yield filename_node
1275
1275
1276 for file_, node in iterfiles():
1276 for file_, node in iterfiles():
1277 filelog = repo.file(file_)
1277 filelog = repo.file(file_)
1278 if not len(filelog):
1278 if not len(filelog):
1279 if node is None:
1279 if node is None:
1280 # A zero count may be a directory or deleted file, so
1280 # A zero count may be a directory or deleted file, so
1281 # try to find matching entries on the slow path.
1281 # try to find matching entries on the slow path.
1282 if follow:
1282 if follow:
1283 raise util.Abort(
1283 raise util.Abort(
1284 _('cannot follow nonexistent file: "%s"') % file_)
1284 _('cannot follow nonexistent file: "%s"') % file_)
1285 raise FileWalkError("Cannot walk via filelog")
1285 raise FileWalkError("Cannot walk via filelog")
1286 else:
1286 else:
1287 continue
1287 continue
1288
1288
1289 if node is None:
1289 if node is None:
1290 last = len(filelog) - 1
1290 last = len(filelog) - 1
1291 else:
1291 else:
1292 last = filelog.rev(node)
1292 last = filelog.rev(node)
1293
1293
1294
1294
1295 # keep track of all ancestors of the file
1295 # keep track of all ancestors of the file
1296 ancestors = set([filelog.linkrev(last)])
1296 ancestors = set([filelog.linkrev(last)])
1297
1297
1298 # iterate from latest to oldest revision
1298 # iterate from latest to oldest revision
1299 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1299 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1300 if not follow:
1300 if not follow:
1301 if rev > maxrev:
1301 if rev > maxrev:
1302 continue
1302 continue
1303 else:
1303 else:
1304 # Note that last might not be the first interesting
1304 # Note that last might not be the first interesting
1305 # rev to us:
1305 # rev to us:
1306 # if the file has been changed after maxrev, we'll
1306 # if the file has been changed after maxrev, we'll
1307 # have linkrev(last) > maxrev, and we still need
1307 # have linkrev(last) > maxrev, and we still need
1308 # to explore the file graph
1308 # to explore the file graph
1309 if rev not in ancestors:
1309 if rev not in ancestors:
1310 continue
1310 continue
1311 # XXX insert 1327 fix here
1311 # XXX insert 1327 fix here
1312 if flparentlinkrevs:
1312 if flparentlinkrevs:
1313 ancestors.update(flparentlinkrevs)
1313 ancestors.update(flparentlinkrevs)
1314
1314
1315 fncache.setdefault(rev, []).append(file_)
1315 fncache.setdefault(rev, []).append(file_)
1316 wanted.add(rev)
1316 wanted.add(rev)
1317 if copied:
1317 if copied:
1318 copies.append(copied)
1318 copies.append(copied)
1319
1319
1320 return wanted
1320 return wanted
1321
1321
1322 def walkchangerevs(repo, match, opts, prepare):
1322 def walkchangerevs(repo, match, opts, prepare):
1323 '''Iterate over files and the revs in which they changed.
1323 '''Iterate over files and the revs in which they changed.
1324
1324
1325 Callers most commonly need to iterate backwards over the history
1325 Callers most commonly need to iterate backwards over the history
1326 in which they are interested. Doing so has awful (quadratic-looking)
1326 in which they are interested. Doing so has awful (quadratic-looking)
1327 performance, so we use iterators in a "windowed" way.
1327 performance, so we use iterators in a "windowed" way.
1328
1328
1329 We walk a window of revisions in the desired order. Within the
1329 We walk a window of revisions in the desired order. Within the
1330 window, we first walk forwards to gather data, then in the desired
1330 window, we first walk forwards to gather data, then in the desired
1331 order (usually backwards) to display it.
1331 order (usually backwards) to display it.
1332
1332
1333 This function returns an iterator yielding contexts. Before
1333 This function returns an iterator yielding contexts. Before
1334 yielding each context, the iterator will first call the prepare
1334 yielding each context, the iterator will first call the prepare
1335 function on each context in the window in forward order.'''
1335 function on each context in the window in forward order.'''
1336
1336
1337 follow = opts.get('follow') or opts.get('follow_first')
1337 follow = opts.get('follow') or opts.get('follow_first')
1338
1338
1339 if opts.get('rev'):
1339 if opts.get('rev'):
1340 revs = scmutil.revrange(repo, opts.get('rev'))
1340 revs = scmutil.revrange(repo, opts.get('rev'))
1341 elif follow:
1341 elif follow:
1342 revs = repo.revs('reverse(:.)')
1342 revs = repo.revs('reverse(:.)')
1343 else:
1343 else:
1344 revs = revset.spanset(repo)
1344 revs = revset.spanset(repo)
1345 revs.reverse()
1345 revs.reverse()
1346 if not revs:
1346 if not revs:
1347 return []
1347 return []
1348 wanted = set()
1348 wanted = set()
1349 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1349 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1350 fncache = {}
1350 fncache = {}
1351 change = repo.changectx
1351 change = repo.changectx
1352
1352
1353 # First step is to fill wanted, the set of revisions that we want to yield.
1353 # First step is to fill wanted, the set of revisions that we want to yield.
1354 # When it does not induce extra cost, we also fill fncache for revisions in
1354 # When it does not induce extra cost, we also fill fncache for revisions in
1355 # wanted: a cache of filenames that were changed (ctx.files()) and that
1355 # wanted: a cache of filenames that were changed (ctx.files()) and that
1356 # match the file filtering conditions.
1356 # match the file filtering conditions.
1357
1357
1358 if not slowpath and not match.files():
1358 if not slowpath and not match.files():
1359 # No files, no patterns. Display all revs.
1359 # No files, no patterns. Display all revs.
1360 wanted = revs
1360 wanted = revs
1361
1361
1362 if not slowpath and match.files():
1362 if not slowpath and match.files():
1363 # We only have to read through the filelog to find wanted revisions
1363 # We only have to read through the filelog to find wanted revisions
1364
1364
1365 try:
1365 try:
1366 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1366 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1367 except FileWalkError:
1367 except FileWalkError:
1368 slowpath = True
1368 slowpath = True
1369
1369
1370 # We decided to fall back to the slowpath because at least one
1370 # We decided to fall back to the slowpath because at least one
1371 # of the paths was not a file. Check to see if at least one of them
1371 # of the paths was not a file. Check to see if at least one of them
1372 # existed in history, otherwise simply return
1372 # existed in history, otherwise simply return
1373 for path in match.files():
1373 for path in match.files():
1374 if path == '.' or path in repo.store:
1374 if path == '.' or path in repo.store:
1375 break
1375 break
1376 else:
1376 else:
1377 return []
1377 return []
1378
1378
1379 if slowpath:
1379 if slowpath:
1380 # We have to read the changelog to match filenames against
1380 # We have to read the changelog to match filenames against
1381 # changed files
1381 # changed files
1382
1382
1383 if follow:
1383 if follow:
1384 raise util.Abort(_('can only follow copies/renames for explicit '
1384 raise util.Abort(_('can only follow copies/renames for explicit '
1385 'filenames'))
1385 'filenames'))
1386
1386
1387 # The slow path checks files modified in every changeset.
1387 # The slow path checks files modified in every changeset.
1388 # This is really slow on large repos, so compute the set lazily.
1388 # This is really slow on large repos, so compute the set lazily.
1389 class lazywantedset(object):
1389 class lazywantedset(object):
1390 def __init__(self):
1390 def __init__(self):
1391 self.set = set()
1391 self.set = set()
1392 self.revs = set(revs)
1392 self.revs = set(revs)
1393
1393
1394 # No need to worry about locality here because it will be accessed
1394 # No need to worry about locality here because it will be accessed
1395 # in the same order as the increasing window below.
1395 # in the same order as the increasing window below.
1396 def __contains__(self, value):
1396 def __contains__(self, value):
1397 if value in self.set:
1397 if value in self.set:
1398 return True
1398 return True
1399 elif not value in self.revs:
1399 elif not value in self.revs:
1400 return False
1400 return False
1401 else:
1401 else:
1402 self.revs.discard(value)
1402 self.revs.discard(value)
1403 ctx = change(value)
1403 ctx = change(value)
1404 matches = filter(match, ctx.files())
1404 matches = filter(match, ctx.files())
1405 if matches:
1405 if matches:
1406 fncache[value] = matches
1406 fncache[value] = matches
1407 self.set.add(value)
1407 self.set.add(value)
1408 return True
1408 return True
1409 return False
1409 return False
1410
1410
1411 def discard(self, value):
1411 def discard(self, value):
1412 self.revs.discard(value)
1412 self.revs.discard(value)
1413 self.set.discard(value)
1413 self.set.discard(value)
1414
1414
1415 wanted = lazywantedset()
1415 wanted = lazywantedset()
1416
1416
1417 class followfilter(object):
1417 class followfilter(object):
1418 def __init__(self, onlyfirst=False):
1418 def __init__(self, onlyfirst=False):
1419 self.startrev = nullrev
1419 self.startrev = nullrev
1420 self.roots = set()
1420 self.roots = set()
1421 self.onlyfirst = onlyfirst
1421 self.onlyfirst = onlyfirst
1422
1422
1423 def match(self, rev):
1423 def match(self, rev):
1424 def realparents(rev):
1424 def realparents(rev):
1425 if self.onlyfirst:
1425 if self.onlyfirst:
1426 return repo.changelog.parentrevs(rev)[0:1]
1426 return repo.changelog.parentrevs(rev)[0:1]
1427 else:
1427 else:
1428 return filter(lambda x: x != nullrev,
1428 return filter(lambda x: x != nullrev,
1429 repo.changelog.parentrevs(rev))
1429 repo.changelog.parentrevs(rev))
1430
1430
1431 if self.startrev == nullrev:
1431 if self.startrev == nullrev:
1432 self.startrev = rev
1432 self.startrev = rev
1433 return True
1433 return True
1434
1434
1435 if rev > self.startrev:
1435 if rev > self.startrev:
1436 # forward: all descendants
1436 # forward: all descendants
1437 if not self.roots:
1437 if not self.roots:
1438 self.roots.add(self.startrev)
1438 self.roots.add(self.startrev)
1439 for parent in realparents(rev):
1439 for parent in realparents(rev):
1440 if parent in self.roots:
1440 if parent in self.roots:
1441 self.roots.add(rev)
1441 self.roots.add(rev)
1442 return True
1442 return True
1443 else:
1443 else:
1444 # backwards: all parents
1444 # backwards: all parents
1445 if not self.roots:
1445 if not self.roots:
1446 self.roots.update(realparents(self.startrev))
1446 self.roots.update(realparents(self.startrev))
1447 if rev in self.roots:
1447 if rev in self.roots:
1448 self.roots.remove(rev)
1448 self.roots.remove(rev)
1449 self.roots.update(realparents(rev))
1449 self.roots.update(realparents(rev))
1450 return True
1450 return True
1451
1451
1452 return False
1452 return False
1453
1453
1454 # it might be worthwhile to do this in the iterator if the rev range
1454 # it might be worthwhile to do this in the iterator if the rev range
1455 # is descending and the prune args are all within that range
1455 # is descending and the prune args are all within that range
1456 for rev in opts.get('prune', ()):
1456 for rev in opts.get('prune', ()):
1457 rev = repo[rev].rev()
1457 rev = repo[rev].rev()
1458 ff = followfilter()
1458 ff = followfilter()
1459 stop = min(revs[0], revs[-1])
1459 stop = min(revs[0], revs[-1])
1460 for x in xrange(rev, stop - 1, -1):
1460 for x in xrange(rev, stop - 1, -1):
1461 if ff.match(x):
1461 if ff.match(x):
1462 wanted = wanted - [x]
1462 wanted = wanted - [x]
1463
1463
1464 # Now that wanted is correctly initialized, we can iterate over the
1464 # Now that wanted is correctly initialized, we can iterate over the
1465 # revision range, yielding only revisions in wanted.
1465 # revision range, yielding only revisions in wanted.
1466 def iterate():
1466 def iterate():
1467 if follow and not match.files():
1467 if follow and not match.files():
1468 ff = followfilter(onlyfirst=opts.get('follow_first'))
1468 ff = followfilter(onlyfirst=opts.get('follow_first'))
1469 def want(rev):
1469 def want(rev):
1470 return ff.match(rev) and rev in wanted
1470 return ff.match(rev) and rev in wanted
1471 else:
1471 else:
1472 def want(rev):
1472 def want(rev):
1473 return rev in wanted
1473 return rev in wanted
1474
1474
1475 it = iter(revs)
1475 it = iter(revs)
1476 stopiteration = False
1476 stopiteration = False
1477 for windowsize in increasingwindows():
1477 for windowsize in increasingwindows():
1478 nrevs = []
1478 nrevs = []
1479 for i in xrange(windowsize):
1479 for i in xrange(windowsize):
1480 try:
1480 try:
1481 rev = it.next()
1481 rev = it.next()
1482 if want(rev):
1482 if want(rev):
1483 nrevs.append(rev)
1483 nrevs.append(rev)
1484 except (StopIteration):
1484 except (StopIteration):
1485 stopiteration = True
1485 stopiteration = True
1486 break
1486 break
1487 for rev in sorted(nrevs):
1487 for rev in sorted(nrevs):
1488 fns = fncache.get(rev)
1488 fns = fncache.get(rev)
1489 ctx = change(rev)
1489 ctx = change(rev)
1490 if not fns:
1490 if not fns:
1491 def fns_generator():
1491 def fns_generator():
1492 for f in ctx.files():
1492 for f in ctx.files():
1493 if match(f):
1493 if match(f):
1494 yield f
1494 yield f
1495 fns = fns_generator()
1495 fns = fns_generator()
1496 prepare(ctx, fns)
1496 prepare(ctx, fns)
1497 for rev in nrevs:
1497 for rev in nrevs:
1498 yield change(rev)
1498 yield change(rev)
1499
1499
1500 if stopiteration:
1500 if stopiteration:
1501 break
1501 break
1502
1502
1503 return iterate()
1503 return iterate()
1504
1504
1505 def _makefollowlogfilematcher(repo, files, followfirst):
1505 def _makefollowlogfilematcher(repo, files, followfirst):
1506 # When displaying a revision with --patch --follow FILE, we have
1506 # When displaying a revision with --patch --follow FILE, we have
1507 # to know which file of the revision must be diffed. With
1507 # to know which file of the revision must be diffed. With
1508 # --follow, we want the names of the ancestors of FILE in the
1508 # --follow, we want the names of the ancestors of FILE in the
1509 # revision, stored in "fcache". "fcache" is populated by
1509 # revision, stored in "fcache". "fcache" is populated by
1510 # reproducing the graph traversal already done by --follow revset
1510 # reproducing the graph traversal already done by --follow revset
1511 # and relating linkrevs to file names (which is not "correct" but
1511 # and relating linkrevs to file names (which is not "correct" but
1512 # good enough).
1512 # good enough).
1513 fcache = {}
1513 fcache = {}
1514 fcacheready = [False]
1514 fcacheready = [False]
1515 pctx = repo['.']
1515 pctx = repo['.']
1516
1516
1517 def populate():
1517 def populate():
1518 for fn in files:
1518 for fn in files:
1519 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1519 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1520 for c in i:
1520 for c in i:
1521 fcache.setdefault(c.linkrev(), set()).add(c.path())
1521 fcache.setdefault(c.linkrev(), set()).add(c.path())
1522
1522
1523 def filematcher(rev):
1523 def filematcher(rev):
1524 if not fcacheready[0]:
1524 if not fcacheready[0]:
1525 # Lazy initialization
1525 # Lazy initialization
1526 fcacheready[0] = True
1526 fcacheready[0] = True
1527 populate()
1527 populate()
1528 return scmutil.matchfiles(repo, fcache.get(rev, []))
1528 return scmutil.matchfiles(repo, fcache.get(rev, []))
1529
1529
1530 return filematcher
1530 return filematcher
1531
1531
1532 def _makenofollowlogfilematcher(repo, pats, opts):
1532 def _makenofollowlogfilematcher(repo, pats, opts):
1533 '''hook for extensions to override the filematcher for non-follow cases'''
1533 '''hook for extensions to override the filematcher for non-follow cases'''
1534 return None
1534 return None
1535
1535
1536 def _makelogrevset(repo, pats, opts, revs):
1536 def _makelogrevset(repo, pats, opts, revs):
1537 """Return (expr, filematcher) where expr is a revset string built
1537 """Return (expr, filematcher) where expr is a revset string built
1538 from log options and file patterns or None. If --stat or --patch
1538 from log options and file patterns or None. If --stat or --patch
1539 are not passed filematcher is None. Otherwise it is a callable
1539 are not passed filematcher is None. Otherwise it is a callable
1540 taking a revision number and returning a match objects filtering
1540 taking a revision number and returning a match objects filtering
1541 the files to be detailed when displaying the revision.
1541 the files to be detailed when displaying the revision.
1542 """
1542 """
1543 opt2revset = {
1543 opt2revset = {
1544 'no_merges': ('not merge()', None),
1544 'no_merges': ('not merge()', None),
1545 'only_merges': ('merge()', None),
1545 'only_merges': ('merge()', None),
1546 '_ancestors': ('ancestors(%(val)s)', None),
1546 '_ancestors': ('ancestors(%(val)s)', None),
1547 '_fancestors': ('_firstancestors(%(val)s)', None),
1547 '_fancestors': ('_firstancestors(%(val)s)', None),
1548 '_descendants': ('descendants(%(val)s)', None),
1548 '_descendants': ('descendants(%(val)s)', None),
1549 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1549 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1550 '_matchfiles': ('_matchfiles(%(val)s)', None),
1550 '_matchfiles': ('_matchfiles(%(val)s)', None),
1551 'date': ('date(%(val)r)', None),
1551 'date': ('date(%(val)r)', None),
1552 'branch': ('branch(%(val)r)', ' or '),
1552 'branch': ('branch(%(val)r)', ' or '),
1553 '_patslog': ('filelog(%(val)r)', ' or '),
1553 '_patslog': ('filelog(%(val)r)', ' or '),
1554 '_patsfollow': ('follow(%(val)r)', ' or '),
1554 '_patsfollow': ('follow(%(val)r)', ' or '),
1555 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1555 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1556 'keyword': ('keyword(%(val)r)', ' or '),
1556 'keyword': ('keyword(%(val)r)', ' or '),
1557 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1557 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1558 'user': ('user(%(val)r)', ' or '),
1558 'user': ('user(%(val)r)', ' or '),
1559 }
1559 }
1560
1560
1561 opts = dict(opts)
1561 opts = dict(opts)
1562 # follow or not follow?
1562 # follow or not follow?
1563 follow = opts.get('follow') or opts.get('follow_first')
1563 follow = opts.get('follow') or opts.get('follow_first')
1564 followfirst = opts.get('follow_first') and 1 or 0
1564 followfirst = opts.get('follow_first') and 1 or 0
1565 # --follow with FILE behaviour depends on revs...
1565 # --follow with FILE behaviour depends on revs...
1566 it = iter(revs)
1566 it = iter(revs)
1567 startrev = it.next()
1567 startrev = it.next()
1568 try:
1568 try:
1569 followdescendants = startrev < it.next()
1569 followdescendants = startrev < it.next()
1570 except (StopIteration):
1570 except (StopIteration):
1571 followdescendants = False
1571 followdescendants = False
1572
1572
1573 # branch and only_branch are really aliases and must be handled at
1573 # branch and only_branch are really aliases and must be handled at
1574 # the same time
1574 # the same time
1575 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1575 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1576 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1576 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1577 # pats/include/exclude are passed to match.match() directly in
1577 # pats/include/exclude are passed to match.match() directly in
1578 # _matchfiles() revset but walkchangerevs() builds its matcher with
1578 # _matchfiles() revset but walkchangerevs() builds its matcher with
1579 # scmutil.match(). The difference is input pats are globbed on
1579 # scmutil.match(). The difference is input pats are globbed on
1580 # platforms without shell expansion (windows).
1580 # platforms without shell expansion (windows).
1581 pctx = repo[None]
1581 pctx = repo[None]
1582 match, pats = scmutil.matchandpats(pctx, pats, opts)
1582 match, pats = scmutil.matchandpats(pctx, pats, opts)
1583 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1583 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1584 if not slowpath:
1584 if not slowpath:
1585 for f in match.files():
1585 for f in match.files():
1586 if follow and f not in pctx:
1586 if follow and f not in pctx:
1587 # If the file exists, it may be a directory, so let it
1587 # If the file exists, it may be a directory, so let it
1588 # take the slow path.
1588 # take the slow path.
1589 if os.path.exists(repo.wjoin(f)):
1589 if os.path.exists(repo.wjoin(f)):
1590 slowpath = True
1590 slowpath = True
1591 continue
1591 continue
1592 else:
1592 else:
1593 raise util.Abort(_('cannot follow file not in parent '
1593 raise util.Abort(_('cannot follow file not in parent '
1594 'revision: "%s"') % f)
1594 'revision: "%s"') % f)
1595 filelog = repo.file(f)
1595 filelog = repo.file(f)
1596 if not filelog:
1596 if not filelog:
1597 # A zero count may be a directory or deleted file, so
1597 # A zero count may be a directory or deleted file, so
1598 # try to find matching entries on the slow path.
1598 # try to find matching entries on the slow path.
1599 if follow:
1599 if follow:
1600 raise util.Abort(
1600 raise util.Abort(
1601 _('cannot follow nonexistent file: "%s"') % f)
1601 _('cannot follow nonexistent file: "%s"') % f)
1602 slowpath = True
1602 slowpath = True
1603
1603
1604 # We decided to fall back to the slowpath because at least one
1604 # We decided to fall back to the slowpath because at least one
1605 # of the paths was not a file. Check to see if at least one of them
1605 # of the paths was not a file. Check to see if at least one of them
1606 # existed in history - in that case, we'll continue down the
1606 # existed in history - in that case, we'll continue down the
1607 # slowpath; otherwise, we can turn off the slowpath
1607 # slowpath; otherwise, we can turn off the slowpath
1608 if slowpath:
1608 if slowpath:
1609 for path in match.files():
1609 for path in match.files():
1610 if path == '.' or path in repo.store:
1610 if path == '.' or path in repo.store:
1611 break
1611 break
1612 else:
1612 else:
1613 slowpath = False
1613 slowpath = False
1614
1614
1615 if slowpath:
1615 if slowpath:
1616 # See walkchangerevs() slow path.
1616 # See walkchangerevs() slow path.
1617 #
1617 #
1618 # pats/include/exclude cannot be represented as separate
1618 # pats/include/exclude cannot be represented as separate
1619 # revset expressions as their filtering logic applies at file
1619 # revset expressions as their filtering logic applies at file
1620 # level. For instance "-I a -X a" matches a revision touching
1620 # level. For instance "-I a -X a" matches a revision touching
1621 # "a" and "b" while "file(a) and not file(b)" does
1621 # "a" and "b" while "file(a) and not file(b)" does
1622 # not. Besides, filesets are evaluated against the working
1622 # not. Besides, filesets are evaluated against the working
1623 # directory.
1623 # directory.
1624 matchargs = ['r:', 'd:relpath']
1624 matchargs = ['r:', 'd:relpath']
1625 for p in pats:
1625 for p in pats:
1626 matchargs.append('p:' + p)
1626 matchargs.append('p:' + p)
1627 for p in opts.get('include', []):
1627 for p in opts.get('include', []):
1628 matchargs.append('i:' + p)
1628 matchargs.append('i:' + p)
1629 for p in opts.get('exclude', []):
1629 for p in opts.get('exclude', []):
1630 matchargs.append('x:' + p)
1630 matchargs.append('x:' + p)
1631 matchargs = ','.join(('%r' % p) for p in matchargs)
1631 matchargs = ','.join(('%r' % p) for p in matchargs)
1632 opts['_matchfiles'] = matchargs
1632 opts['_matchfiles'] = matchargs
1633 else:
1633 else:
1634 if follow:
1634 if follow:
1635 fpats = ('_patsfollow', '_patsfollowfirst')
1635 fpats = ('_patsfollow', '_patsfollowfirst')
1636 fnopats = (('_ancestors', '_fancestors'),
1636 fnopats = (('_ancestors', '_fancestors'),
1637 ('_descendants', '_fdescendants'))
1637 ('_descendants', '_fdescendants'))
1638 if pats:
1638 if pats:
1639 # follow() revset interprets its file argument as a
1639 # follow() revset interprets its file argument as a
1640 # manifest entry, so use match.files(), not pats.
1640 # manifest entry, so use match.files(), not pats.
1641 opts[fpats[followfirst]] = list(match.files())
1641 opts[fpats[followfirst]] = list(match.files())
1642 else:
1642 else:
1643 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1643 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1644 else:
1644 else:
1645 opts['_patslog'] = list(pats)
1645 opts['_patslog'] = list(pats)
1646
1646
1647 filematcher = None
1647 filematcher = None
1648 if opts.get('patch') or opts.get('stat'):
1648 if opts.get('patch') or opts.get('stat'):
1649 # When following files, track renames via a special matcher.
1649 # When following files, track renames via a special matcher.
1650 # If we're forced to take the slowpath it means we're following
1650 # If we're forced to take the slowpath it means we're following
1651 # at least one pattern/directory, so don't bother with rename tracking.
1651 # at least one pattern/directory, so don't bother with rename tracking.
1652 if follow and not match.always() and not slowpath:
1652 if follow and not match.always() and not slowpath:
1653 # _makelogfilematcher expects its files argument to be relative to
1653 # _makelogfilematcher expects its files argument to be relative to
1654 # the repo root, so use match.files(), not pats.
1654 # the repo root, so use match.files(), not pats.
1655 filematcher = _makefollowlogfilematcher(repo, match.files(),
1655 filematcher = _makefollowlogfilematcher(repo, match.files(),
1656 followfirst)
1656 followfirst)
1657 else:
1657 else:
1658 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1658 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1659 if filematcher is None:
1659 if filematcher is None:
1660 filematcher = lambda rev: match
1660 filematcher = lambda rev: match
1661
1661
1662 expr = []
1662 expr = []
1663 for op, val in opts.iteritems():
1663 for op, val in opts.iteritems():
1664 if not val:
1664 if not val:
1665 continue
1665 continue
1666 if op not in opt2revset:
1666 if op not in opt2revset:
1667 continue
1667 continue
1668 revop, andor = opt2revset[op]
1668 revop, andor = opt2revset[op]
1669 if '%(val)' not in revop:
1669 if '%(val)' not in revop:
1670 expr.append(revop)
1670 expr.append(revop)
1671 else:
1671 else:
1672 if not isinstance(val, list):
1672 if not isinstance(val, list):
1673 e = revop % {'val': val}
1673 e = revop % {'val': val}
1674 else:
1674 else:
1675 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1675 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1676 expr.append(e)
1676 expr.append(e)
1677
1677
1678 if expr:
1678 if expr:
1679 expr = '(' + ' and '.join(expr) + ')'
1679 expr = '(' + ' and '.join(expr) + ')'
1680 else:
1680 else:
1681 expr = None
1681 expr = None
1682 return expr, filematcher
1682 return expr, filematcher
1683
1683
1684 def getgraphlogrevs(repo, pats, opts):
1684 def getgraphlogrevs(repo, pats, opts):
1685 """Return (revs, expr, filematcher) where revs is an iterable of
1685 """Return (revs, expr, filematcher) where revs is an iterable of
1686 revision numbers, expr is a revset string built from log options
1686 revision numbers, expr is a revset string built from log options
1687 and file patterns or None, and used to filter 'revs'. If --stat or
1687 and file patterns or None, and used to filter 'revs'. If --stat or
1688 --patch are not passed filematcher is None. Otherwise it is a
1688 --patch are not passed filematcher is None. Otherwise it is a
1689 callable taking a revision number and returning a match objects
1689 callable taking a revision number and returning a match objects
1690 filtering the files to be detailed when displaying the revision.
1690 filtering the files to be detailed when displaying the revision.
1691 """
1691 """
1692 if not len(repo):
1692 if not len(repo):
1693 return [], None, None
1693 return [], None, None
1694 limit = loglimit(opts)
1694 limit = loglimit(opts)
1695 # Default --rev value depends on --follow but --follow behaviour
1695 # Default --rev value depends on --follow but --follow behaviour
1696 # depends on revisions resolved from --rev...
1696 # depends on revisions resolved from --rev...
1697 follow = opts.get('follow') or opts.get('follow_first')
1697 follow = opts.get('follow') or opts.get('follow_first')
1698 possiblyunsorted = False # whether revs might need sorting
1698 possiblyunsorted = False # whether revs might need sorting
1699 if opts.get('rev'):
1699 if opts.get('rev'):
1700 revs = scmutil.revrange(repo, opts['rev'])
1700 revs = scmutil.revrange(repo, opts['rev'])
1701 # Don't sort here because _makelogrevset might depend on the
1701 # Don't sort here because _makelogrevset might depend on the
1702 # order of revs
1702 # order of revs
1703 possiblyunsorted = True
1703 possiblyunsorted = True
1704 else:
1704 else:
1705 if follow and len(repo) > 0:
1705 if follow and len(repo) > 0:
1706 revs = repo.revs('reverse(:.)')
1706 revs = repo.revs('reverse(:.)')
1707 else:
1707 else:
1708 revs = revset.spanset(repo)
1708 revs = revset.spanset(repo)
1709 revs.reverse()
1709 revs.reverse()
1710 if not revs:
1710 if not revs:
1711 return revset.baseset(), None, None
1711 return revset.baseset(), None, None
1712 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1712 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1713 if possiblyunsorted:
1713 if possiblyunsorted:
1714 revs.sort(reverse=True)
1714 revs.sort(reverse=True)
1715 if expr:
1715 if expr:
1716 # Revset matchers often operate faster on revisions in changelog
1716 # Revset matchers often operate faster on revisions in changelog
1717 # order, because most filters deal with the changelog.
1717 # order, because most filters deal with the changelog.
1718 revs.reverse()
1718 revs.reverse()
1719 matcher = revset.match(repo.ui, expr)
1719 matcher = revset.match(repo.ui, expr)
1720 # Revset matches can reorder revisions. "A or B" typically returns
1720 # Revset matches can reorder revisions. "A or B" typically returns
1721 # returns the revision matching A then the revision matching B. Sort
1721 # returns the revision matching A then the revision matching B. Sort
1722 # again to fix that.
1722 # again to fix that.
1723 revs = matcher(repo, revs)
1723 revs = matcher(repo, revs)
1724 revs.sort(reverse=True)
1724 revs.sort(reverse=True)
1725 if limit is not None:
1725 if limit is not None:
1726 limitedrevs = revset.baseset()
1726 limitedrevs = revset.baseset()
1727 for idx, rev in enumerate(revs):
1727 for idx, rev in enumerate(revs):
1728 if idx >= limit:
1728 if idx >= limit:
1729 break
1729 break
1730 limitedrevs.append(rev)
1730 limitedrevs.append(rev)
1731 revs = limitedrevs
1731 revs = limitedrevs
1732
1732
1733 return revs, expr, filematcher
1733 return revs, expr, filematcher
1734
1734
1735 def getlogrevs(repo, pats, opts):
1735 def getlogrevs(repo, pats, opts):
1736 """Return (revs, expr, filematcher) where revs is an iterable of
1736 """Return (revs, expr, filematcher) where revs is an iterable of
1737 revision numbers, expr is a revset string built from log options
1737 revision numbers, expr is a revset string built from log options
1738 and file patterns or None, and used to filter 'revs'. If --stat or
1738 and file patterns or None, and used to filter 'revs'. If --stat or
1739 --patch are not passed filematcher is None. Otherwise it is a
1739 --patch are not passed filematcher is None. Otherwise it is a
1740 callable taking a revision number and returning a match objects
1740 callable taking a revision number and returning a match objects
1741 filtering the files to be detailed when displaying the revision.
1741 filtering the files to be detailed when displaying the revision.
1742 """
1742 """
1743 limit = loglimit(opts)
1743 limit = loglimit(opts)
1744 # Default --rev value depends on --follow but --follow behaviour
1744 # Default --rev value depends on --follow but --follow behaviour
1745 # depends on revisions resolved from --rev...
1745 # depends on revisions resolved from --rev...
1746 follow = opts.get('follow') or opts.get('follow_first')
1746 follow = opts.get('follow') or opts.get('follow_first')
1747 if opts.get('rev'):
1747 if opts.get('rev'):
1748 revs = scmutil.revrange(repo, opts['rev'])
1748 revs = scmutil.revrange(repo, opts['rev'])
1749 elif follow:
1749 elif follow:
1750 revs = repo.revs('reverse(:.)')
1750 revs = repo.revs('reverse(:.)')
1751 else:
1751 else:
1752 revs = revset.spanset(repo)
1752 revs = revset.spanset(repo)
1753 revs.reverse()
1753 revs.reverse()
1754 if not revs:
1754 if not revs:
1755 return revset.baseset([]), None, None
1755 return revset.baseset([]), None, None
1756 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1756 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1757 if expr:
1757 if expr:
1758 # Revset matchers often operate faster on revisions in changelog
1758 # Revset matchers often operate faster on revisions in changelog
1759 # order, because most filters deal with the changelog.
1759 # order, because most filters deal with the changelog.
1760 if not opts.get('rev'):
1760 if not opts.get('rev'):
1761 revs.reverse()
1761 revs.reverse()
1762 matcher = revset.match(repo.ui, expr)
1762 matcher = revset.match(repo.ui, expr)
1763 # Revset matches can reorder revisions. "A or B" typically returns
1763 # Revset matches can reorder revisions. "A or B" typically returns
1764 # returns the revision matching A then the revision matching B. Sort
1764 # returns the revision matching A then the revision matching B. Sort
1765 # again to fix that.
1765 # again to fix that.
1766 revs = matcher(repo, revs)
1766 revs = matcher(repo, revs)
1767 if not opts.get('rev'):
1767 if not opts.get('rev'):
1768 revs.sort(reverse=True)
1768 revs.sort(reverse=True)
1769 if limit is not None:
1769 if limit is not None:
1770 count = 0
1770 count = 0
1771 limitedrevs = revset.baseset([])
1771 limitedrevs = revset.baseset([])
1772 it = iter(revs)
1772 it = iter(revs)
1773 while count < limit:
1773 while count < limit:
1774 try:
1774 try:
1775 limitedrevs.append(it.next())
1775 limitedrevs.append(it.next())
1776 except (StopIteration):
1776 except (StopIteration):
1777 break
1777 break
1778 count += 1
1778 count += 1
1779 revs = limitedrevs
1779 revs = limitedrevs
1780
1780
1781 return revs, expr, filematcher
1781 return revs, expr, filematcher
1782
1782
1783 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1783 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1784 filematcher=None):
1784 filematcher=None):
1785 seen, state = [], graphmod.asciistate()
1785 seen, state = [], graphmod.asciistate()
1786 for rev, type, ctx, parents in dag:
1786 for rev, type, ctx, parents in dag:
1787 char = 'o'
1787 char = 'o'
1788 if ctx.node() in showparents:
1788 if ctx.node() in showparents:
1789 char = '@'
1789 char = '@'
1790 elif ctx.obsolete():
1790 elif ctx.obsolete():
1791 char = 'x'
1791 char = 'x'
1792 copies = None
1792 copies = None
1793 if getrenamed and ctx.rev():
1793 if getrenamed and ctx.rev():
1794 copies = []
1794 copies = []
1795 for fn in ctx.files():
1795 for fn in ctx.files():
1796 rename = getrenamed(fn, ctx.rev())
1796 rename = getrenamed(fn, ctx.rev())
1797 if rename:
1797 if rename:
1798 copies.append((fn, rename[0]))
1798 copies.append((fn, rename[0]))
1799 revmatchfn = None
1799 revmatchfn = None
1800 if filematcher is not None:
1800 if filematcher is not None:
1801 revmatchfn = filematcher(ctx.rev())
1801 revmatchfn = filematcher(ctx.rev())
1802 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1802 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1803 lines = displayer.hunk.pop(rev).split('\n')
1803 lines = displayer.hunk.pop(rev).split('\n')
1804 if not lines[-1]:
1804 if not lines[-1]:
1805 del lines[-1]
1805 del lines[-1]
1806 displayer.flush(rev)
1806 displayer.flush(rev)
1807 edges = edgefn(type, char, lines, seen, rev, parents)
1807 edges = edgefn(type, char, lines, seen, rev, parents)
1808 for type, char, lines, coldata in edges:
1808 for type, char, lines, coldata in edges:
1809 graphmod.ascii(ui, state, type, char, lines, coldata)
1809 graphmod.ascii(ui, state, type, char, lines, coldata)
1810 displayer.close()
1810 displayer.close()
1811
1811
1812 def graphlog(ui, repo, *pats, **opts):
1812 def graphlog(ui, repo, *pats, **opts):
1813 # Parameters are identical to log command ones
1813 # Parameters are identical to log command ones
1814 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1814 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1815 revdag = graphmod.dagwalker(repo, revs)
1815 revdag = graphmod.dagwalker(repo, revs)
1816
1816
1817 getrenamed = None
1817 getrenamed = None
1818 if opts.get('copies'):
1818 if opts.get('copies'):
1819 endrev = None
1819 endrev = None
1820 if opts.get('rev'):
1820 if opts.get('rev'):
1821 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1821 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1822 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1822 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1823 displayer = show_changeset(ui, repo, opts, buffered=True)
1823 displayer = show_changeset(ui, repo, opts, buffered=True)
1824 showparents = [ctx.node() for ctx in repo[None].parents()]
1824 showparents = [ctx.node() for ctx in repo[None].parents()]
1825 displaygraph(ui, revdag, displayer, showparents,
1825 displaygraph(ui, revdag, displayer, showparents,
1826 graphmod.asciiedges, getrenamed, filematcher)
1826 graphmod.asciiedges, getrenamed, filematcher)
1827
1827
1828 def checkunsupportedgraphflags(pats, opts):
1828 def checkunsupportedgraphflags(pats, opts):
1829 for op in ["newest_first"]:
1829 for op in ["newest_first"]:
1830 if op in opts and opts[op]:
1830 if op in opts and opts[op]:
1831 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1831 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1832 % op.replace("_", "-"))
1832 % op.replace("_", "-"))
1833
1833
1834 def graphrevs(repo, nodes, opts):
1834 def graphrevs(repo, nodes, opts):
1835 limit = loglimit(opts)
1835 limit = loglimit(opts)
1836 nodes.reverse()
1836 nodes.reverse()
1837 if limit is not None:
1837 if limit is not None:
1838 nodes = nodes[:limit]
1838 nodes = nodes[:limit]
1839 return graphmod.nodes(repo, nodes)
1839 return graphmod.nodes(repo, nodes)
1840
1840
1841 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1841 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1842 join = lambda f: os.path.join(prefix, f)
1842 join = lambda f: os.path.join(prefix, f)
1843 bad = []
1843 bad = []
1844 oldbad = match.bad
1844 oldbad = match.bad
1845 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1845 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1846 names = []
1846 names = []
1847 wctx = repo[None]
1847 wctx = repo[None]
1848 cca = None
1848 cca = None
1849 abort, warn = scmutil.checkportabilityalert(ui)
1849 abort, warn = scmutil.checkportabilityalert(ui)
1850 if abort or warn:
1850 if abort or warn:
1851 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1851 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1852 for f in repo.walk(match):
1852 for f in repo.walk(match):
1853 exact = match.exact(f)
1853 exact = match.exact(f)
1854 if exact or not explicitonly and f not in repo.dirstate:
1854 if exact or not explicitonly and f not in repo.dirstate:
1855 if cca:
1855 if cca:
1856 cca(f)
1856 cca(f)
1857 names.append(f)
1857 names.append(f)
1858 if ui.verbose or not exact:
1858 if ui.verbose or not exact:
1859 ui.status(_('adding %s\n') % match.rel(join(f)))
1859 ui.status(_('adding %s\n') % match.rel(join(f)))
1860
1860
1861 for subpath in sorted(wctx.substate):
1861 for subpath in sorted(wctx.substate):
1862 sub = wctx.sub(subpath)
1862 sub = wctx.sub(subpath)
1863 try:
1863 try:
1864 submatch = matchmod.narrowmatcher(subpath, match)
1864 submatch = matchmod.narrowmatcher(subpath, match)
1865 if listsubrepos:
1865 if listsubrepos:
1866 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1866 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1867 False))
1867 False))
1868 else:
1868 else:
1869 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1869 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1870 True))
1870 True))
1871 except error.LookupError:
1871 except error.LookupError:
1872 ui.status(_("skipping missing subrepository: %s\n")
1872 ui.status(_("skipping missing subrepository: %s\n")
1873 % join(subpath))
1873 % join(subpath))
1874
1874
1875 if not dryrun:
1875 if not dryrun:
1876 rejected = wctx.add(names, prefix)
1876 rejected = wctx.add(names, prefix)
1877 bad.extend(f for f in rejected if f in match.files())
1877 bad.extend(f for f in rejected if f in match.files())
1878 return bad
1878 return bad
1879
1879
1880 def forget(ui, repo, match, prefix, explicitonly):
1880 def forget(ui, repo, match, prefix, explicitonly):
1881 join = lambda f: os.path.join(prefix, f)
1881 join = lambda f: os.path.join(prefix, f)
1882 bad = []
1882 bad = []
1883 oldbad = match.bad
1883 oldbad = match.bad
1884 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1884 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1885 wctx = repo[None]
1885 wctx = repo[None]
1886 forgot = []
1886 forgot = []
1887 s = repo.status(match=match, clean=True)
1887 s = repo.status(match=match, clean=True)
1888 forget = sorted(s[0] + s[1] + s[3] + s[6])
1888 forget = sorted(s[0] + s[1] + s[3] + s[6])
1889 if explicitonly:
1889 if explicitonly:
1890 forget = [f for f in forget if match.exact(f)]
1890 forget = [f for f in forget if match.exact(f)]
1891
1891
1892 for subpath in sorted(wctx.substate):
1892 for subpath in sorted(wctx.substate):
1893 sub = wctx.sub(subpath)
1893 sub = wctx.sub(subpath)
1894 try:
1894 try:
1895 submatch = matchmod.narrowmatcher(subpath, match)
1895 submatch = matchmod.narrowmatcher(subpath, match)
1896 subbad, subforgot = sub.forget(ui, submatch, prefix)
1896 subbad, subforgot = sub.forget(ui, submatch, prefix)
1897 bad.extend([subpath + '/' + f for f in subbad])
1897 bad.extend([subpath + '/' + f for f in subbad])
1898 forgot.extend([subpath + '/' + f for f in subforgot])
1898 forgot.extend([subpath + '/' + f for f in subforgot])
1899 except error.LookupError:
1899 except error.LookupError:
1900 ui.status(_("skipping missing subrepository: %s\n")
1900 ui.status(_("skipping missing subrepository: %s\n")
1901 % join(subpath))
1901 % join(subpath))
1902
1902
1903 if not explicitonly:
1903 if not explicitonly:
1904 for f in match.files():
1904 for f in match.files():
1905 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1905 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1906 if f not in forgot:
1906 if f not in forgot:
1907 if os.path.exists(match.rel(join(f))):
1907 if os.path.exists(match.rel(join(f))):
1908 ui.warn(_('not removing %s: '
1908 ui.warn(_('not removing %s: '
1909 'file is already untracked\n')
1909 'file is already untracked\n')
1910 % match.rel(join(f)))
1910 % match.rel(join(f)))
1911 bad.append(f)
1911 bad.append(f)
1912
1912
1913 for f in forget:
1913 for f in forget:
1914 if ui.verbose or not match.exact(f):
1914 if ui.verbose or not match.exact(f):
1915 ui.status(_('removing %s\n') % match.rel(join(f)))
1915 ui.status(_('removing %s\n') % match.rel(join(f)))
1916
1916
1917 rejected = wctx.forget(forget, prefix)
1917 rejected = wctx.forget(forget, prefix)
1918 bad.extend(f for f in rejected if f in match.files())
1918 bad.extend(f for f in rejected if f in match.files())
1919 forgot.extend(forget)
1919 forgot.extend(forget)
1920 return bad, forgot
1920 return bad, forgot
1921
1921
1922 def cat(ui, repo, ctx, matcher, prefix, **opts):
1922 def cat(ui, repo, ctx, matcher, prefix, **opts):
1923 err = 1
1923 err = 1
1924
1924
1925 def write(path):
1925 def write(path):
1926 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1926 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1927 pathname=os.path.join(prefix, path))
1927 pathname=os.path.join(prefix, path))
1928 data = ctx[path].data()
1928 data = ctx[path].data()
1929 if opts.get('decode'):
1929 if opts.get('decode'):
1930 data = repo.wwritedata(path, data)
1930 data = repo.wwritedata(path, data)
1931 fp.write(data)
1931 fp.write(data)
1932 fp.close()
1932 fp.close()
1933
1933
1934 # Automation often uses hg cat on single files, so special case it
1934 # Automation often uses hg cat on single files, so special case it
1935 # for performance to avoid the cost of parsing the manifest.
1935 # for performance to avoid the cost of parsing the manifest.
1936 if len(matcher.files()) == 1 and not matcher.anypats():
1936 if len(matcher.files()) == 1 and not matcher.anypats():
1937 file = matcher.files()[0]
1937 file = matcher.files()[0]
1938 mf = repo.manifest
1938 mf = repo.manifest
1939 mfnode = ctx._changeset[0]
1939 mfnode = ctx._changeset[0]
1940 if mf.find(mfnode, file)[0]:
1940 if mf.find(mfnode, file)[0]:
1941 write(file)
1941 write(file)
1942 return 0
1942 return 0
1943
1943
1944 # Don't warn about "missing" files that are really in subrepos
1944 # Don't warn about "missing" files that are really in subrepos
1945 bad = matcher.bad
1945 bad = matcher.bad
1946
1946
1947 def badfn(path, msg):
1947 def badfn(path, msg):
1948 for subpath in ctx.substate:
1948 for subpath in ctx.substate:
1949 if path.startswith(subpath):
1949 if path.startswith(subpath):
1950 return
1950 return
1951 bad(path, msg)
1951 bad(path, msg)
1952
1952
1953 matcher.bad = badfn
1953 matcher.bad = badfn
1954
1954
1955 for abs in ctx.walk(matcher):
1955 for abs in ctx.walk(matcher):
1956 write(abs)
1956 write(abs)
1957 err = 0
1957 err = 0
1958
1958
1959 matcher.bad = bad
1959 matcher.bad = bad
1960
1960
1961 for subpath in sorted(ctx.substate):
1961 for subpath in sorted(ctx.substate):
1962 sub = ctx.sub(subpath)
1962 sub = ctx.sub(subpath)
1963 try:
1963 try:
1964 submatch = matchmod.narrowmatcher(subpath, matcher)
1964 submatch = matchmod.narrowmatcher(subpath, matcher)
1965
1965
1966 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1966 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1967 **opts):
1967 **opts):
1968 err = 0
1968 err = 0
1969 except error.RepoLookupError:
1969 except error.RepoLookupError:
1970 ui.status(_("skipping missing subrepository: %s\n")
1970 ui.status(_("skipping missing subrepository: %s\n")
1971 % os.path.join(prefix, subpath))
1971 % os.path.join(prefix, subpath))
1972
1972
1973 return err
1973 return err
1974
1974
1975 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1975 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1976 '''reproduce copies from fromrev to rev in the dirstate
1976 '''reproduce copies from fromrev to rev in the dirstate
1977
1977
1978 If skiprev is specified, it's a revision that should be used to
1978 If skiprev is specified, it's a revision that should be used to
1979 filter copy records. Any copies that occur between fromrev and
1979 filter copy records. Any copies that occur between fromrev and
1980 skiprev will not be duplicated, even if they appear in the set of
1980 skiprev will not be duplicated, even if they appear in the set of
1981 copies between fromrev and rev.
1981 copies between fromrev and rev.
1982 '''
1982 '''
1983 exclude = {}
1983 exclude = {}
1984 if skiprev is not None:
1984 if skiprev is not None:
1985 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1985 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1986 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1986 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1987 # copies.pathcopies returns backward renames, so dst might not
1987 # copies.pathcopies returns backward renames, so dst might not
1988 # actually be in the dirstate
1988 # actually be in the dirstate
1989 if dst in exclude:
1989 if dst in exclude:
1990 continue
1990 continue
1991 if repo.dirstate[dst] in "nma":
1991 if repo.dirstate[dst] in "nma":
1992 repo.dirstate.copy(src, dst)
1992 repo.dirstate.copy(src, dst)
1993
1993
1994 def commit(ui, repo, commitfunc, pats, opts):
1994 def commit(ui, repo, commitfunc, pats, opts):
1995 '''commit the specified files or all outstanding changes'''
1995 '''commit the specified files or all outstanding changes'''
1996 date = opts.get('date')
1996 date = opts.get('date')
1997 if date:
1997 if date:
1998 opts['date'] = util.parsedate(date)
1998 opts['date'] = util.parsedate(date)
1999 message = logmessage(ui, opts)
1999 message = logmessage(ui, opts)
2000
2000
2001 # extract addremove carefully -- this function can be called from a command
2001 # extract addremove carefully -- this function can be called from a command
2002 # that doesn't support addremove
2002 # that doesn't support addremove
2003 if opts.get('addremove'):
2003 if opts.get('addremove'):
2004 scmutil.addremove(repo, pats, opts)
2004 scmutil.addremove(repo, pats, opts)
2005
2005
2006 return commitfunc(ui, repo, message,
2006 return commitfunc(ui, repo, message,
2007 scmutil.match(repo[None], pats, opts), opts)
2007 scmutil.match(repo[None], pats, opts), opts)
2008
2008
2009 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2009 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2010 ui.note(_('amending changeset %s\n') % old)
2010 ui.note(_('amending changeset %s\n') % old)
2011 base = old.p1()
2011 base = old.p1()
2012
2012
2013 wlock = lock = newid = None
2013 wlock = lock = newid = None
2014 try:
2014 try:
2015 wlock = repo.wlock()
2015 wlock = repo.wlock()
2016 lock = repo.lock()
2016 lock = repo.lock()
2017 tr = repo.transaction('amend')
2017 tr = repo.transaction('amend')
2018 try:
2018 try:
2019 # See if we got a message from -m or -l, if not, open the editor
2019 # See if we got a message from -m or -l, if not, open the editor
2020 # with the message of the changeset to amend
2020 # with the message of the changeset to amend
2021 message = logmessage(ui, opts)
2021 message = logmessage(ui, opts)
2022 # ensure logfile does not conflict with later enforcement of the
2022 # ensure logfile does not conflict with later enforcement of the
2023 # message. potential logfile content has been processed by
2023 # message. potential logfile content has been processed by
2024 # `logmessage` anyway.
2024 # `logmessage` anyway.
2025 opts.pop('logfile')
2025 opts.pop('logfile')
2026 # First, do a regular commit to record all changes in the working
2026 # First, do a regular commit to record all changes in the working
2027 # directory (if there are any)
2027 # directory (if there are any)
2028 ui.callhooks = False
2028 ui.callhooks = False
2029 currentbookmark = repo._bookmarkcurrent
2029 currentbookmark = repo._bookmarkcurrent
2030 try:
2030 try:
2031 repo._bookmarkcurrent = None
2031 repo._bookmarkcurrent = None
2032 opts['message'] = 'temporary amend commit for %s' % old
2032 opts['message'] = 'temporary amend commit for %s' % old
2033 node = commit(ui, repo, commitfunc, pats, opts)
2033 node = commit(ui, repo, commitfunc, pats, opts)
2034 finally:
2034 finally:
2035 repo._bookmarkcurrent = currentbookmark
2035 repo._bookmarkcurrent = currentbookmark
2036 ui.callhooks = True
2036 ui.callhooks = True
2037 ctx = repo[node]
2037 ctx = repo[node]
2038
2038
2039 # Participating changesets:
2039 # Participating changesets:
2040 #
2040 #
2041 # node/ctx o - new (intermediate) commit that contains changes
2041 # node/ctx o - new (intermediate) commit that contains changes
2042 # | from working dir to go into amending commit
2042 # | from working dir to go into amending commit
2043 # | (or a workingctx if there were no changes)
2043 # | (or a workingctx if there were no changes)
2044 # |
2044 # |
2045 # old o - changeset to amend
2045 # old o - changeset to amend
2046 # |
2046 # |
2047 # base o - parent of amending changeset
2047 # base o - parent of amending changeset
2048
2048
2049 # Update extra dict from amended commit (e.g. to preserve graft
2049 # Update extra dict from amended commit (e.g. to preserve graft
2050 # source)
2050 # source)
2051 extra.update(old.extra())
2051 extra.update(old.extra())
2052
2052
2053 # Also update it from the intermediate commit or from the wctx
2053 # Also update it from the intermediate commit or from the wctx
2054 extra.update(ctx.extra())
2054 extra.update(ctx.extra())
2055
2055
2056 if len(old.parents()) > 1:
2056 if len(old.parents()) > 1:
2057 # ctx.files() isn't reliable for merges, so fall back to the
2057 # ctx.files() isn't reliable for merges, so fall back to the
2058 # slower repo.status() method
2058 # slower repo.status() method
2059 files = set([fn for st in repo.status(base, old)[:3]
2059 files = set([fn for st in repo.status(base, old)[:3]
2060 for fn in st])
2060 for fn in st])
2061 else:
2061 else:
2062 files = set(old.files())
2062 files = set(old.files())
2063
2063
2064 # Second, we use either the commit we just did, or if there were no
2064 # Second, we use either the commit we just did, or if there were no
2065 # changes the parent of the working directory as the version of the
2065 # changes the parent of the working directory as the version of the
2066 # files in the final amend commit
2066 # files in the final amend commit
2067 if node:
2067 if node:
2068 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2068 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2069
2069
2070 user = ctx.user()
2070 user = ctx.user()
2071 date = ctx.date()
2071 date = ctx.date()
2072 # Recompute copies (avoid recording a -> b -> a)
2072 # Recompute copies (avoid recording a -> b -> a)
2073 copied = copies.pathcopies(base, ctx)
2073 copied = copies.pathcopies(base, ctx)
2074
2074
2075 # Prune files which were reverted by the updates: if old
2075 # Prune files which were reverted by the updates: if old
2076 # introduced file X and our intermediate commit, node,
2076 # introduced file X and our intermediate commit, node,
2077 # renamed that file, then those two files are the same and
2077 # renamed that file, then those two files are the same and
2078 # we can discard X from our list of files. Likewise if X
2078 # we can discard X from our list of files. Likewise if X
2079 # was deleted, it's no longer relevant
2079 # was deleted, it's no longer relevant
2080 files.update(ctx.files())
2080 files.update(ctx.files())
2081
2081
2082 def samefile(f):
2082 def samefile(f):
2083 if f in ctx.manifest():
2083 if f in ctx.manifest():
2084 a = ctx.filectx(f)
2084 a = ctx.filectx(f)
2085 if f in base.manifest():
2085 if f in base.manifest():
2086 b = base.filectx(f)
2086 b = base.filectx(f)
2087 return (not a.cmp(b)
2087 return (not a.cmp(b)
2088 and a.flags() == b.flags())
2088 and a.flags() == b.flags())
2089 else:
2089 else:
2090 return False
2090 return False
2091 else:
2091 else:
2092 return f not in base.manifest()
2092 return f not in base.manifest()
2093 files = [f for f in files if not samefile(f)]
2093 files = [f for f in files if not samefile(f)]
2094
2094
2095 def filectxfn(repo, ctx_, path):
2095 def filectxfn(repo, ctx_, path):
2096 try:
2096 try:
2097 fctx = ctx[path]
2097 fctx = ctx[path]
2098 flags = fctx.flags()
2098 flags = fctx.flags()
2099 mctx = context.memfilectx(repo,
2099 mctx = context.memfilectx(repo,
2100 fctx.path(), fctx.data(),
2100 fctx.path(), fctx.data(),
2101 islink='l' in flags,
2101 islink='l' in flags,
2102 isexec='x' in flags,
2102 isexec='x' in flags,
2103 copied=copied.get(path))
2103 copied=copied.get(path))
2104 return mctx
2104 return mctx
2105 except KeyError:
2105 except KeyError:
2106 raise IOError
2106 raise IOError
2107 else:
2107 else:
2108 ui.note(_('copying changeset %s to %s\n') % (old, base))
2108 ui.note(_('copying changeset %s to %s\n') % (old, base))
2109
2109
2110 # Use version of files as in the old cset
2110 # Use version of files as in the old cset
2111 def filectxfn(repo, ctx_, path):
2111 def filectxfn(repo, ctx_, path):
2112 try:
2112 try:
2113 return old.filectx(path)
2113 return old.filectx(path)
2114 except KeyError:
2114 except KeyError:
2115 raise IOError
2115 raise IOError
2116
2116
2117 user = opts.get('user') or old.user()
2117 user = opts.get('user') or old.user()
2118 date = opts.get('date') or old.date()
2118 date = opts.get('date') or old.date()
2119 editform = 'commit.amend'
2119 editform = 'commit.amend'
2120 editor = getcommiteditor(editform=editform, **opts)
2120 editor = getcommiteditor(editform=editform, **opts)
2121 if not message:
2121 if not message:
2122 editor = getcommiteditor(edit=True, editform=editform)
2122 editor = getcommiteditor(edit=True, editform=editform)
2123 message = old.description()
2123 message = old.description()
2124
2124
2125 pureextra = extra.copy()
2125 pureextra = extra.copy()
2126 extra['amend_source'] = old.hex()
2126 extra['amend_source'] = old.hex()
2127
2127
2128 new = context.memctx(repo,
2128 new = context.memctx(repo,
2129 parents=[base.node(), old.p2().node()],
2129 parents=[base.node(), old.p2().node()],
2130 text=message,
2130 text=message,
2131 files=files,
2131 files=files,
2132 filectxfn=filectxfn,
2132 filectxfn=filectxfn,
2133 user=user,
2133 user=user,
2134 date=date,
2134 date=date,
2135 extra=extra,
2135 extra=extra,
2136 editor=editor)
2136 editor=editor)
2137
2137
2138 newdesc = changelog.stripdesc(new.description())
2138 newdesc = changelog.stripdesc(new.description())
2139 if ((not node)
2139 if ((not node)
2140 and newdesc == old.description()
2140 and newdesc == old.description()
2141 and user == old.user()
2141 and user == old.user()
2142 and date == old.date()
2142 and date == old.date()
2143 and pureextra == old.extra()):
2143 and pureextra == old.extra()):
2144 # nothing changed. continuing here would create a new node
2144 # nothing changed. continuing here would create a new node
2145 # anyway because of the amend_source noise.
2145 # anyway because of the amend_source noise.
2146 #
2146 #
2147 # This not what we expect from amend.
2147 # This not what we expect from amend.
2148 return old.node()
2148 return old.node()
2149
2149
2150 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2150 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2151 try:
2151 try:
2152 if opts.get('secret'):
2152 if opts.get('secret'):
2153 commitphase = 'secret'
2153 commitphase = 'secret'
2154 else:
2154 else:
2155 commitphase = old.phase()
2155 commitphase = old.phase()
2156 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2156 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2157 newid = repo.commitctx(new)
2157 newid = repo.commitctx(new)
2158 finally:
2158 finally:
2159 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2159 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2160 if newid != old.node():
2160 if newid != old.node():
2161 # Reroute the working copy parent to the new changeset
2161 # Reroute the working copy parent to the new changeset
2162 repo.setparents(newid, nullid)
2162 repo.setparents(newid, nullid)
2163
2163
2164 # Move bookmarks from old parent to amend commit
2164 # Move bookmarks from old parent to amend commit
2165 bms = repo.nodebookmarks(old.node())
2165 bms = repo.nodebookmarks(old.node())
2166 if bms:
2166 if bms:
2167 marks = repo._bookmarks
2167 marks = repo._bookmarks
2168 for bm in bms:
2168 for bm in bms:
2169 marks[bm] = newid
2169 marks[bm] = newid
2170 marks.write()
2170 marks.write()
2171 #commit the whole amend process
2171 #commit the whole amend process
2172 if obsolete._enabled and newid != old.node():
2172 if obsolete._enabled and newid != old.node():
2173 # mark the new changeset as successor of the rewritten one
2173 # mark the new changeset as successor of the rewritten one
2174 new = repo[newid]
2174 new = repo[newid]
2175 obs = [(old, (new,))]
2175 obs = [(old, (new,))]
2176 if node:
2176 if node:
2177 obs.append((ctx, ()))
2177 obs.append((ctx, ()))
2178
2178
2179 obsolete.createmarkers(repo, obs)
2179 obsolete.createmarkers(repo, obs)
2180 tr.close()
2180 tr.close()
2181 finally:
2181 finally:
2182 tr.release()
2182 tr.release()
2183 if (not obsolete._enabled) and newid != old.node():
2183 if (not obsolete._enabled) and newid != old.node():
2184 # Strip the intermediate commit (if there was one) and the amended
2184 # Strip the intermediate commit (if there was one) and the amended
2185 # commit
2185 # commit
2186 if node:
2186 if node:
2187 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2187 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2188 ui.note(_('stripping amended changeset %s\n') % old)
2188 ui.note(_('stripping amended changeset %s\n') % old)
2189 repair.strip(ui, repo, old.node(), topic='amend-backup')
2189 repair.strip(ui, repo, old.node(), topic='amend-backup')
2190 finally:
2190 finally:
2191 if newid is None:
2191 if newid is None:
2192 repo.dirstate.invalidate()
2192 repo.dirstate.invalidate()
2193 lockmod.release(lock, wlock)
2193 lockmod.release(lock, wlock)
2194 return newid
2194 return newid
2195
2195
2196 def commiteditor(repo, ctx, subs, editform=''):
2196 def commiteditor(repo, ctx, subs, editform=''):
2197 if ctx.description():
2197 if ctx.description():
2198 return ctx.description()
2198 return ctx.description()
2199 return commitforceeditor(repo, ctx, subs, editform=editform)
2199 return commitforceeditor(repo, ctx, subs, editform=editform)
2200
2200
2201 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2201 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2202 editform=''):
2202 editform=''):
2203 if not extramsg:
2203 if not extramsg:
2204 extramsg = _("Leave message empty to abort commit.")
2204 extramsg = _("Leave message empty to abort commit.")
2205
2205
2206 forms = [e for e in editform.split('.') if e]
2206 forms = [e for e in editform.split('.') if e]
2207 forms.insert(0, 'changeset')
2207 forms.insert(0, 'changeset')
2208 while forms:
2208 while forms:
2209 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2209 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2210 if tmpl:
2210 if tmpl:
2211 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2211 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2212 break
2212 break
2213 forms.pop()
2213 forms.pop()
2214 else:
2214 else:
2215 committext = buildcommittext(repo, ctx, subs, extramsg)
2215 committext = buildcommittext(repo, ctx, subs, extramsg)
2216
2216
2217 # run editor in the repository root
2217 # run editor in the repository root
2218 olddir = os.getcwd()
2218 olddir = os.getcwd()
2219 os.chdir(repo.root)
2219 os.chdir(repo.root)
2220 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2220 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2221 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2221 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2222 os.chdir(olddir)
2222 os.chdir(olddir)
2223
2223
2224 if finishdesc:
2224 if finishdesc:
2225 text = finishdesc(text)
2225 text = finishdesc(text)
2226 if not text.strip():
2226 if not text.strip():
2227 raise util.Abort(_("empty commit message"))
2227 raise util.Abort(_("empty commit message"))
2228
2228
2229 return text
2229 return text
2230
2230
2231 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2231 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2232 ui = repo.ui
2232 ui = repo.ui
2233 tmpl, mapfile = gettemplate(ui, tmpl, None)
2233 tmpl, mapfile = gettemplate(ui, tmpl, None)
2234
2234
2235 try:
2235 try:
2236 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2236 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2237 except SyntaxError, inst:
2237 except SyntaxError, inst:
2238 raise util.Abort(inst.args[0])
2238 raise util.Abort(inst.args[0])
2239
2239
2240 for k, v in repo.ui.configitems('committemplate'):
2240 for k, v in repo.ui.configitems('committemplate'):
2241 if k != 'changeset':
2241 if k != 'changeset':
2242 t.t.cache[k] = v
2242 t.t.cache[k] = v
2243
2243
2244 if not extramsg:
2244 if not extramsg:
2245 extramsg = '' # ensure that extramsg is string
2245 extramsg = '' # ensure that extramsg is string
2246
2246
2247 ui.pushbuffer()
2247 ui.pushbuffer()
2248 t.show(ctx, extramsg=extramsg)
2248 t.show(ctx, extramsg=extramsg)
2249 return ui.popbuffer()
2249 return ui.popbuffer()
2250
2250
2251 def buildcommittext(repo, ctx, subs, extramsg):
2251 def buildcommittext(repo, ctx, subs, extramsg):
2252 edittext = []
2252 edittext = []
2253 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2253 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2254 if ctx.description():
2254 if ctx.description():
2255 edittext.append(ctx.description())
2255 edittext.append(ctx.description())
2256 edittext.append("")
2256 edittext.append("")
2257 edittext.append("") # Empty line between message and comments.
2257 edittext.append("") # Empty line between message and comments.
2258 edittext.append(_("HG: Enter commit message."
2258 edittext.append(_("HG: Enter commit message."
2259 " Lines beginning with 'HG:' are removed."))
2259 " Lines beginning with 'HG:' are removed."))
2260 edittext.append("HG: %s" % extramsg)
2260 edittext.append("HG: %s" % extramsg)
2261 edittext.append("HG: --")
2261 edittext.append("HG: --")
2262 edittext.append(_("HG: user: %s") % ctx.user())
2262 edittext.append(_("HG: user: %s") % ctx.user())
2263 if ctx.p2():
2263 if ctx.p2():
2264 edittext.append(_("HG: branch merge"))
2264 edittext.append(_("HG: branch merge"))
2265 if ctx.branch():
2265 if ctx.branch():
2266 edittext.append(_("HG: branch '%s'") % ctx.branch())
2266 edittext.append(_("HG: branch '%s'") % ctx.branch())
2267 if bookmarks.iscurrent(repo):
2267 if bookmarks.iscurrent(repo):
2268 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2268 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2269 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2269 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2270 edittext.extend([_("HG: added %s") % f for f in added])
2270 edittext.extend([_("HG: added %s") % f for f in added])
2271 edittext.extend([_("HG: changed %s") % f for f in modified])
2271 edittext.extend([_("HG: changed %s") % f for f in modified])
2272 edittext.extend([_("HG: removed %s") % f for f in removed])
2272 edittext.extend([_("HG: removed %s") % f for f in removed])
2273 if not added and not modified and not removed:
2273 if not added and not modified and not removed:
2274 edittext.append(_("HG: no files changed"))
2274 edittext.append(_("HG: no files changed"))
2275 edittext.append("")
2275 edittext.append("")
2276
2276
2277 return "\n".join(edittext)
2277 return "\n".join(edittext)
2278
2278
2279 def commitstatus(repo, node, branch, bheads=None, opts={}):
2279 def commitstatus(repo, node, branch, bheads=None, opts={}):
2280 ctx = repo[node]
2280 ctx = repo[node]
2281 parents = ctx.parents()
2281 parents = ctx.parents()
2282
2282
2283 if (not opts.get('amend') and bheads and node not in bheads and not
2283 if (not opts.get('amend') and bheads and node not in bheads and not
2284 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2284 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2285 repo.ui.status(_('created new head\n'))
2285 repo.ui.status(_('created new head\n'))
2286 # The message is not printed for initial roots. For the other
2286 # The message is not printed for initial roots. For the other
2287 # changesets, it is printed in the following situations:
2287 # changesets, it is printed in the following situations:
2288 #
2288 #
2289 # Par column: for the 2 parents with ...
2289 # Par column: for the 2 parents with ...
2290 # N: null or no parent
2290 # N: null or no parent
2291 # B: parent is on another named branch
2291 # B: parent is on another named branch
2292 # C: parent is a regular non head changeset
2292 # C: parent is a regular non head changeset
2293 # H: parent was a branch head of the current branch
2293 # H: parent was a branch head of the current branch
2294 # Msg column: whether we print "created new head" message
2294 # Msg column: whether we print "created new head" message
2295 # In the following, it is assumed that there already exists some
2295 # In the following, it is assumed that there already exists some
2296 # initial branch heads of the current branch, otherwise nothing is
2296 # initial branch heads of the current branch, otherwise nothing is
2297 # printed anyway.
2297 # printed anyway.
2298 #
2298 #
2299 # Par Msg Comment
2299 # Par Msg Comment
2300 # N N y additional topo root
2300 # N N y additional topo root
2301 #
2301 #
2302 # B N y additional branch root
2302 # B N y additional branch root
2303 # C N y additional topo head
2303 # C N y additional topo head
2304 # H N n usual case
2304 # H N n usual case
2305 #
2305 #
2306 # B B y weird additional branch root
2306 # B B y weird additional branch root
2307 # C B y branch merge
2307 # C B y branch merge
2308 # H B n merge with named branch
2308 # H B n merge with named branch
2309 #
2309 #
2310 # C C y additional head from merge
2310 # C C y additional head from merge
2311 # C H n merge with a head
2311 # C H n merge with a head
2312 #
2312 #
2313 # H H n head merge: head count decreases
2313 # H H n head merge: head count decreases
2314
2314
2315 if not opts.get('close_branch'):
2315 if not opts.get('close_branch'):
2316 for r in parents:
2316 for r in parents:
2317 if r.closesbranch() and r.branch() == branch:
2317 if r.closesbranch() and r.branch() == branch:
2318 repo.ui.status(_('reopening closed branch head %d\n') % r)
2318 repo.ui.status(_('reopening closed branch head %d\n') % r)
2319
2319
2320 if repo.ui.debugflag:
2320 if repo.ui.debugflag:
2321 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2321 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2322 elif repo.ui.verbose:
2322 elif repo.ui.verbose:
2323 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2323 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2324
2324
2325 def revert(ui, repo, ctx, parents, *pats, **opts):
2325 def revert(ui, repo, ctx, parents, *pats, **opts):
2326 parent, p2 = parents
2326 parent, p2 = parents
2327 node = ctx.node()
2327 node = ctx.node()
2328
2328
2329 mf = ctx.manifest()
2329 mf = ctx.manifest()
2330 if node == p2:
2330 if node == p2:
2331 parent = p2
2331 parent = p2
2332 if node == parent:
2332 if node == parent:
2333 pmf = mf
2333 pmf = mf
2334 else:
2334 else:
2335 pmf = None
2335 pmf = None
2336
2336
2337 # need all matching names in dirstate and manifest of target rev,
2337 # need all matching names in dirstate and manifest of target rev,
2338 # so have to walk both. do not print errors if files exist in one
2338 # so have to walk both. do not print errors if files exist in one
2339 # but not other.
2339 # but not other.
2340
2340
2341 # `names` is a mapping for all elements in working copy and target revision
2341 # `names` is a mapping for all elements in working copy and target revision
2342 # The mapping is in the form:
2342 # The mapping is in the form:
2343 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2343 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2344 names = {}
2344 names = {}
2345
2345
2346 wlock = repo.wlock()
2346 wlock = repo.wlock()
2347 try:
2347 try:
2348 ## filling of the `names` mapping
2348 ## filling of the `names` mapping
2349 # walk dirstate to fill `names`
2349 # walk dirstate to fill `names`
2350
2350
2351 m = scmutil.match(repo[None], pats, opts)
2351 m = scmutil.match(repo[None], pats, opts)
2352 m.bad = lambda x, y: False
2352 m.bad = lambda x, y: False
2353 for abs in repo.walk(m):
2353 for abs in repo.walk(m):
2354 names[abs] = m.rel(abs), m.exact(abs)
2354 names[abs] = m.rel(abs), m.exact(abs)
2355
2355
2356 # walk target manifest to fill `names`
2356 # walk target manifest to fill `names`
2357
2357
2358 def badfn(path, msg):
2358 def badfn(path, msg):
2359 if path in names:
2359 if path in names:
2360 return
2360 return
2361 if path in ctx.substate:
2361 if path in ctx.substate:
2362 return
2362 return
2363 path_ = path + '/'
2363 path_ = path + '/'
2364 for f in names:
2364 for f in names:
2365 if f.startswith(path_):
2365 if f.startswith(path_):
2366 return
2366 return
2367 ui.warn("%s: %s\n" % (m.rel(path), msg))
2367 ui.warn("%s: %s\n" % (m.rel(path), msg))
2368
2368
2369 m = scmutil.match(ctx, pats, opts)
2369 m = scmutil.match(ctx, pats, opts)
2370 m.bad = badfn
2370 m.bad = badfn
2371 for abs in ctx.walk(m):
2371 for abs in ctx.walk(m):
2372 if abs not in names:
2372 if abs not in names:
2373 names[abs] = m.rel(abs), m.exact(abs)
2373 names[abs] = m.rel(abs), m.exact(abs)
2374
2374
2375 # get the list of subrepos that must be reverted
2375 # get the list of subrepos that must be reverted
2376 targetsubs = sorted(s for s in ctx.substate if m(s))
2376 targetsubs = sorted(s for s in ctx.substate if m(s))
2377
2377
2378 # Find status of all file in `names`.
2378 # Find status of all file in `names`.
2379 m = scmutil.matchfiles(repo, names)
2379 m = scmutil.matchfiles(repo, names)
2380
2380
2381 changes = repo.status(node1=node, match=m, clean=True)
2381 changes = repo.status(node1=node, match=m,
2382 unknown=True, ignored=True, clean=True)
2382 modified = set(changes[0])
2383 modified = set(changes[0])
2383 added = set(changes[1])
2384 added = set(changes[1])
2384 removed = set(changes[2])
2385 removed = set(changes[2])
2385 _deleted = set(changes[3])
2386 _deleted = set(changes[3])
2387 unknown = set(changes[4])
2388 unknown.update(changes[5])
2386 clean = set(changes[6])
2389 clean = set(changes[6])
2387
2390
2388 # split between files known in target manifest and the others
2391 # split between files known in target manifest and the others
2389 smf = set(mf)
2392 smf = set(mf)
2390
2393
2391 # determine the exact nature of the deleted changesets
2394 # determine the exact nature of the deleted changesets
2392 _deletedadded = _deleted - smf
2395 _deletedadded = _deleted - smf
2393 _deletedmodified = _deleted - _deletedadded
2396 _deletedmodified = _deleted - _deletedadded
2394 added |= _deletedadded
2397 added |= _deletedadded
2395 modified |= _deletedmodified
2398 modified |= _deletedmodified
2396
2399
2397 # We need to account for the state of file in the dirstate
2400 # We need to account for the state of file in the dirstate
2398 #
2401 #
2399 # Even, when we revert agains something else than parent. this will
2402 # Even, when we revert agains something else than parent. this will
2400 # slightly alter the behavior of revert (doing back up or not, delete
2403 # slightly alter the behavior of revert (doing back up or not, delete
2401 # or just forget etc)
2404 # or just forget etc)
2402 if parent == node:
2405 if parent == node:
2403 dsmodified = modified
2406 dsmodified = modified
2404 dsadded = added
2407 dsadded = added
2405 dsremoved = removed
2408 dsremoved = removed
2406 modified, added, removed = set(), set(), set()
2409 modified, added, removed = set(), set(), set()
2407 else:
2410 else:
2408 changes = repo.status(node1=parent, match=m)
2411 changes = repo.status(node1=parent, match=m)
2409 dsmodified = set(changes[0])
2412 dsmodified = set(changes[0])
2410 dsadded = set(changes[1])
2413 dsadded = set(changes[1])
2411 dsremoved = set(changes[2])
2414 dsremoved = set(changes[2])
2412
2415
2413 # only take into account for removes between wc and target
2416 # only take into account for removes between wc and target
2414 clean |= dsremoved - removed
2417 clean |= dsremoved - removed
2415 dsremoved &= removed
2418 dsremoved &= removed
2416 # distinct between dirstate remove and other
2419 # distinct between dirstate remove and other
2417 removed -= dsremoved
2420 removed -= dsremoved
2418
2421
2419 # tell newly modified apart.
2422 # tell newly modified apart.
2420 dsmodified &= modified
2423 dsmodified &= modified
2421 dsmodified |= modified & dsadded # dirstate added may needs backup
2424 dsmodified |= modified & dsadded # dirstate added may needs backup
2422 modified -= dsmodified
2425 modified -= dsmodified
2423
2426
2424 # There are three categories of added files
2427 # There are three categories of added files
2425 #
2428 #
2426 # 1. addition that just happened in the dirstate
2429 # 1. addition that just happened in the dirstate
2427 # (should be forgotten)
2430 # (should be forgotten)
2428 # 2. file is added since target revision and has local changes
2431 # 2. file is added since target revision and has local changes
2429 # (should be backed up and removed)
2432 # (should be backed up and removed)
2430 # 3. file is added since target revision and is clean
2433 # 3. file is added since target revision and is clean
2431 # (should be removed)
2434 # (should be removed)
2432 #
2435 #
2433 # However we do not need to split them yet. The current revert code
2436 # However we do not need to split them yet. The current revert code
2434 # will automatically recognize (1) when performing operation. And
2437 # will automatically recognize (1) when performing operation. And
2435 # the backup system is currently unabled to handle (2).
2438 # the backup system is currently unabled to handle (2).
2436 #
2439 #
2437 # So we just put them all in the same group.
2440 # So we just put them all in the same group.
2438 dsadded = added
2441 dsadded = added
2439
2442
2440 # in case of merge, files that are actually added can be reported as
2443 # in case of merge, files that are actually added can be reported as
2441 # modified, we need to post process the result
2444 # modified, we need to post process the result
2442 if p2 != nullid:
2445 if p2 != nullid:
2443 if pmf is None:
2446 if pmf is None:
2444 # only need parent manifest in the merge case,
2447 # only need parent manifest in the merge case,
2445 # so do not read by default
2448 # so do not read by default
2446 pmf = repo[parent].manifest()
2449 pmf = repo[parent].manifest()
2447 mergeadd = dsmodified - set(pmf)
2450 mergeadd = dsmodified - set(pmf)
2448 dsadded |= mergeadd
2451 dsadded |= mergeadd
2449 dsmodified -= mergeadd
2452 dsmodified -= mergeadd
2450
2453
2451 # if f is a rename, update `names` to also revert the source
2454 # if f is a rename, update `names` to also revert the source
2452 cwd = repo.getcwd()
2455 cwd = repo.getcwd()
2453 for f in dsadded:
2456 for f in dsadded:
2454 src = repo.dirstate.copied(f)
2457 src = repo.dirstate.copied(f)
2455 # XXX should we check for rename down to target node?
2458 # XXX should we check for rename down to target node?
2456 if src and src not in names and repo.dirstate[src] == 'r':
2459 if src and src not in names and repo.dirstate[src] == 'r':
2457 dsremoved.add(src)
2460 dsremoved.add(src)
2458 names[src] = (repo.pathto(src, cwd), True)
2461 names[src] = (repo.pathto(src, cwd), True)
2459
2462
2460 ## computation of the action to performs on `names` content.
2463 ## computation of the action to performs on `names` content.
2461
2464
2462 def removeforget(abs):
2465 def removeforget(abs):
2463 if repo.dirstate[abs] == 'a':
2466 if repo.dirstate[abs] == 'a':
2464 return _('forgetting %s\n')
2467 return _('forgetting %s\n')
2465 return _('removing %s\n')
2468 return _('removing %s\n')
2466
2469
2467 # action to be actually performed by revert
2470 # action to be actually performed by revert
2468 # (<list of file>, message>) tuple
2471 # (<list of file>, message>) tuple
2469 actions = {'revert': ([], _('reverting %s\n')),
2472 actions = {'revert': ([], _('reverting %s\n')),
2470 'add': ([], _('adding %s\n')),
2473 'add': ([], _('adding %s\n')),
2471 'remove': ([], removeforget),
2474 'remove': ([], removeforget),
2472 'undelete': ([], _('undeleting %s\n')),
2475 'undelete': ([], _('undeleting %s\n')),
2473 'noop': (None, _('no changes needed to %s\n')),
2476 'noop': (None, _('no changes needed to %s\n')),
2477 'unknown': (None, _('file not managed: %s\n')),
2474 }
2478 }
2475
2479
2476
2480
2477 # should we do a backup?
2481 # should we do a backup?
2478 backup = not opts.get('no_backup')
2482 backup = not opts.get('no_backup')
2479 discard = False
2483 discard = False
2480
2484
2481 disptable = (
2485 disptable = (
2482 # dispatch table:
2486 # dispatch table:
2483 # file state
2487 # file state
2484 # action
2488 # action
2485 # make backup
2489 # make backup
2486 (modified, actions['revert'], discard),
2490 (modified, actions['revert'], discard),
2487 (dsmodified, actions['revert'], backup),
2491 (dsmodified, actions['revert'], backup),
2488 (dsadded, actions['remove'], backup),
2492 (dsadded, actions['remove'], backup),
2489 (removed, actions['add'], backup),
2493 (removed, actions['add'], backup),
2490 (dsremoved, actions['undelete'], backup),
2494 (dsremoved, actions['undelete'], backup),
2491 (clean, actions['noop'], discard),
2495 (clean, actions['noop'], discard),
2496 (unknown, actions['unknown'], discard),
2492 )
2497 )
2493
2498
2494 for abs, (rel, exact) in sorted(names.items()):
2499 for abs, (rel, exact) in sorted(names.items()):
2495 # target file to be touch on disk (relative to cwd)
2500 # target file to be touch on disk (relative to cwd)
2496 target = repo.wjoin(abs)
2501 target = repo.wjoin(abs)
2497 # search the entry in the dispatch table.
2502 # search the entry in the dispatch table.
2498 # if the file is in any of these sets, it was touched in the working
2503 # if the file is in any of these sets, it was touched in the working
2499 # directory parent and we are sure it needs to be reverted.
2504 # directory parent and we are sure it needs to be reverted.
2500 for table, (xlist, msg), dobackup in disptable:
2505 for table, (xlist, msg), dobackup in disptable:
2501 if abs not in table:
2506 if abs not in table:
2502 continue
2507 continue
2503 if xlist is not None:
2508 if xlist is not None:
2504 xlist.append(abs)
2509 xlist.append(abs)
2505 if (dobackup and os.path.lexists(target) and
2510 if (dobackup and os.path.lexists(target) and
2506 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2511 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2507 bakname = "%s.orig" % rel
2512 bakname = "%s.orig" % rel
2508 ui.note(_('saving current version of %s as %s\n') %
2513 ui.note(_('saving current version of %s as %s\n') %
2509 (rel, bakname))
2514 (rel, bakname))
2510 if not opts.get('dry_run'):
2515 if not opts.get('dry_run'):
2511 util.rename(target, bakname)
2516 util.rename(target, bakname)
2512 if ui.verbose or not exact:
2517 if ui.verbose or not exact:
2513 if not isinstance(msg, basestring):
2518 if not isinstance(msg, basestring):
2514 msg = msg(abs)
2519 msg = msg(abs)
2515 ui.status(msg % rel)
2520 ui.status(msg % rel)
2516 elif exact:
2521 elif exact:
2517 ui.warn(msg % rel)
2522 ui.warn(msg % rel)
2518 break
2523 break
2519 else:
2524 else:
2520 # Not touched in current dirstate.
2525 # Not touched in current dirstate.
2521
2526
2522 # file is unknown in parent, restore older version or ignore.
2527 # file is unknown in parent, restore older version or ignore.
2523 if abs not in repo.dirstate:
2528 if abs not in repo.dirstate:
2524 if exact:
2529 if exact:
2525 ui.warn(_('file not managed: %s\n') % rel)
2530 ui.warn(_('file not managed: %s\n') % rel)
2526 continue
2531 continue
2527
2532
2528
2533
2529 if not opts.get('dry_run'):
2534 if not opts.get('dry_run'):
2530 _performrevert(repo, parents, ctx, actions)
2535 _performrevert(repo, parents, ctx, actions)
2531
2536
2532 if targetsubs:
2537 if targetsubs:
2533 # Revert the subrepos on the revert list
2538 # Revert the subrepos on the revert list
2534 for sub in targetsubs:
2539 for sub in targetsubs:
2535 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2540 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2536 finally:
2541 finally:
2537 wlock.release()
2542 wlock.release()
2538
2543
2539 def _performrevert(repo, parents, ctx, actions):
2544 def _performrevert(repo, parents, ctx, actions):
2540 """function that actually perform all the actions computed for revert
2545 """function that actually perform all the actions computed for revert
2541
2546
2542 This is an independent function to let extension to plug in and react to
2547 This is an independent function to let extension to plug in and react to
2543 the imminent revert.
2548 the imminent revert.
2544
2549
2545 Make sure you have the working directory locked when calling this function.
2550 Make sure you have the working directory locked when calling this function.
2546 """
2551 """
2547 parent, p2 = parents
2552 parent, p2 = parents
2548 node = ctx.node()
2553 node = ctx.node()
2549 def checkout(f):
2554 def checkout(f):
2550 fc = ctx[f]
2555 fc = ctx[f]
2551 repo.wwrite(f, fc.data(), fc.flags())
2556 repo.wwrite(f, fc.data(), fc.flags())
2552
2557
2553 audit_path = pathutil.pathauditor(repo.root)
2558 audit_path = pathutil.pathauditor(repo.root)
2554 for f in actions['remove'][0]:
2559 for f in actions['remove'][0]:
2555 if repo.dirstate[f] == 'a':
2560 if repo.dirstate[f] == 'a':
2556 repo.dirstate.drop(f)
2561 repo.dirstate.drop(f)
2557 continue
2562 continue
2558 audit_path(f)
2563 audit_path(f)
2559 try:
2564 try:
2560 util.unlinkpath(repo.wjoin(f))
2565 util.unlinkpath(repo.wjoin(f))
2561 except OSError:
2566 except OSError:
2562 pass
2567 pass
2563 repo.dirstate.remove(f)
2568 repo.dirstate.remove(f)
2564
2569
2565 normal = None
2570 normal = None
2566 if node == parent:
2571 if node == parent:
2567 # We're reverting to our parent. If possible, we'd like status
2572 # We're reverting to our parent. If possible, we'd like status
2568 # to report the file as clean. We have to use normallookup for
2573 # to report the file as clean. We have to use normallookup for
2569 # merges to avoid losing information about merged/dirty files.
2574 # merges to avoid losing information about merged/dirty files.
2570 if p2 != nullid:
2575 if p2 != nullid:
2571 normal = repo.dirstate.normallookup
2576 normal = repo.dirstate.normallookup
2572 else:
2577 else:
2573 normal = repo.dirstate.normal
2578 normal = repo.dirstate.normal
2574 for f in actions['revert'][0]:
2579 for f in actions['revert'][0]:
2575 checkout(f)
2580 checkout(f)
2576 if normal:
2581 if normal:
2577 normal(f)
2582 normal(f)
2578
2583
2579 for f in actions['add'][0]:
2584 for f in actions['add'][0]:
2580 checkout(f)
2585 checkout(f)
2581 repo.dirstate.add(f)
2586 repo.dirstate.add(f)
2582
2587
2583 normal = repo.dirstate.normallookup
2588 normal = repo.dirstate.normallookup
2584 if node == parent and p2 == nullid:
2589 if node == parent and p2 == nullid:
2585 normal = repo.dirstate.normal
2590 normal = repo.dirstate.normal
2586 for f in actions['undelete'][0]:
2591 for f in actions['undelete'][0]:
2587 checkout(f)
2592 checkout(f)
2588 normal(f)
2593 normal(f)
2589
2594
2590 copied = copies.pathcopies(repo[parent], ctx)
2595 copied = copies.pathcopies(repo[parent], ctx)
2591
2596
2592 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2597 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2593 if f in copied:
2598 if f in copied:
2594 repo.dirstate.copy(copied[f], f)
2599 repo.dirstate.copy(copied[f], f)
2595
2600
2596 def command(table):
2601 def command(table):
2597 """Returns a function object to be used as a decorator for making commands.
2602 """Returns a function object to be used as a decorator for making commands.
2598
2603
2599 This function receives a command table as its argument. The table should
2604 This function receives a command table as its argument. The table should
2600 be a dict.
2605 be a dict.
2601
2606
2602 The returned function can be used as a decorator for adding commands
2607 The returned function can be used as a decorator for adding commands
2603 to that command table. This function accepts multiple arguments to define
2608 to that command table. This function accepts multiple arguments to define
2604 a command.
2609 a command.
2605
2610
2606 The first argument is the command name.
2611 The first argument is the command name.
2607
2612
2608 The options argument is an iterable of tuples defining command arguments.
2613 The options argument is an iterable of tuples defining command arguments.
2609 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2614 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2610
2615
2611 The synopsis argument defines a short, one line summary of how to use the
2616 The synopsis argument defines a short, one line summary of how to use the
2612 command. This shows up in the help output.
2617 command. This shows up in the help output.
2613
2618
2614 The norepo argument defines whether the command does not require a
2619 The norepo argument defines whether the command does not require a
2615 local repository. Most commands operate against a repository, thus the
2620 local repository. Most commands operate against a repository, thus the
2616 default is False.
2621 default is False.
2617
2622
2618 The optionalrepo argument defines whether the command optionally requires
2623 The optionalrepo argument defines whether the command optionally requires
2619 a local repository.
2624 a local repository.
2620
2625
2621 The inferrepo argument defines whether to try to find a repository from the
2626 The inferrepo argument defines whether to try to find a repository from the
2622 command line arguments. If True, arguments will be examined for potential
2627 command line arguments. If True, arguments will be examined for potential
2623 repository locations. See ``findrepo()``. If a repository is found, it
2628 repository locations. See ``findrepo()``. If a repository is found, it
2624 will be used.
2629 will be used.
2625 """
2630 """
2626 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2631 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2627 inferrepo=False):
2632 inferrepo=False):
2628 def decorator(func):
2633 def decorator(func):
2629 if synopsis:
2634 if synopsis:
2630 table[name] = func, list(options), synopsis
2635 table[name] = func, list(options), synopsis
2631 else:
2636 else:
2632 table[name] = func, list(options)
2637 table[name] = func, list(options)
2633
2638
2634 if norepo:
2639 if norepo:
2635 # Avoid import cycle.
2640 # Avoid import cycle.
2636 import commands
2641 import commands
2637 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2642 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2638
2643
2639 if optionalrepo:
2644 if optionalrepo:
2640 import commands
2645 import commands
2641 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2646 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2642
2647
2643 if inferrepo:
2648 if inferrepo:
2644 import commands
2649 import commands
2645 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2650 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2646
2651
2647 return func
2652 return func
2648 return decorator
2653 return decorator
2649
2654
2650 return cmd
2655 return cmd
2651
2656
2652 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2657 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2653 # commands.outgoing. "missing" is "missing" of the result of
2658 # commands.outgoing. "missing" is "missing" of the result of
2654 # "findcommonoutgoing()"
2659 # "findcommonoutgoing()"
2655 outgoinghooks = util.hooks()
2660 outgoinghooks = util.hooks()
2656
2661
2657 # a list of (ui, repo) functions called by commands.summary
2662 # a list of (ui, repo) functions called by commands.summary
2658 summaryhooks = util.hooks()
2663 summaryhooks = util.hooks()
2659
2664
2660 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2665 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2661 #
2666 #
2662 # functions should return tuple of booleans below, if 'changes' is None:
2667 # functions should return tuple of booleans below, if 'changes' is None:
2663 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2668 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2664 #
2669 #
2665 # otherwise, 'changes' is a tuple of tuples below:
2670 # otherwise, 'changes' is a tuple of tuples below:
2666 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2671 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2667 # - (desturl, destbranch, destpeer, outgoing)
2672 # - (desturl, destbranch, destpeer, outgoing)
2668 summaryremotehooks = util.hooks()
2673 summaryremotehooks = util.hooks()
2669
2674
2670 # A list of state files kept by multistep operations like graft.
2675 # A list of state files kept by multistep operations like graft.
2671 # Since graft cannot be aborted, it is considered 'clearable' by update.
2676 # Since graft cannot be aborted, it is considered 'clearable' by update.
2672 # note: bisect is intentionally excluded
2677 # note: bisect is intentionally excluded
2673 # (state file, clearable, allowcommit, error, hint)
2678 # (state file, clearable, allowcommit, error, hint)
2674 unfinishedstates = [
2679 unfinishedstates = [
2675 ('graftstate', True, False, _('graft in progress'),
2680 ('graftstate', True, False, _('graft in progress'),
2676 _("use 'hg graft --continue' or 'hg update' to abort")),
2681 _("use 'hg graft --continue' or 'hg update' to abort")),
2677 ('updatestate', True, False, _('last update was interrupted'),
2682 ('updatestate', True, False, _('last update was interrupted'),
2678 _("use 'hg update' to get a consistent checkout"))
2683 _("use 'hg update' to get a consistent checkout"))
2679 ]
2684 ]
2680
2685
2681 def checkunfinished(repo, commit=False):
2686 def checkunfinished(repo, commit=False):
2682 '''Look for an unfinished multistep operation, like graft, and abort
2687 '''Look for an unfinished multistep operation, like graft, and abort
2683 if found. It's probably good to check this right before
2688 if found. It's probably good to check this right before
2684 bailifchanged().
2689 bailifchanged().
2685 '''
2690 '''
2686 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2691 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2687 if commit and allowcommit:
2692 if commit and allowcommit:
2688 continue
2693 continue
2689 if repo.vfs.exists(f):
2694 if repo.vfs.exists(f):
2690 raise util.Abort(msg, hint=hint)
2695 raise util.Abort(msg, hint=hint)
2691
2696
2692 def clearunfinished(repo):
2697 def clearunfinished(repo):
2693 '''Check for unfinished operations (as above), and clear the ones
2698 '''Check for unfinished operations (as above), and clear the ones
2694 that are clearable.
2699 that are clearable.
2695 '''
2700 '''
2696 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2701 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2697 if not clearable and repo.vfs.exists(f):
2702 if not clearable and repo.vfs.exists(f):
2698 raise util.Abort(msg, hint=hint)
2703 raise util.Abort(msg, hint=hint)
2699 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2704 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2700 if clearable and repo.vfs.exists(f):
2705 if clearable and repo.vfs.exists(f):
2701 util.unlink(repo.join(f))
2706 util.unlink(repo.join(f))
General Comments 0
You need to be logged in to leave comments. Login now