##// END OF EJS Templates
clfilter: remove any explicit revision number from default cmdutil range...
Pierre-Yves David -
r17676:f87683a1 default
parent child Browse files
Show More
@@ -1,1957 +1,1958 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import subrepo, context, repair, bookmarks, graphmod, revset, phases, obsolete
13 import subrepo, context, repair, bookmarks, graphmod, revset, phases, obsolete
14 import lock as lockmod
14 import lock as lockmod
15
15
16 def parsealiases(cmd):
16 def parsealiases(cmd):
17 return cmd.lstrip("^").split("|")
17 return cmd.lstrip("^").split("|")
18
18
19 def findpossible(cmd, table, strict=False):
19 def findpossible(cmd, table, strict=False):
20 """
20 """
21 Return cmd -> (aliases, command table entry)
21 Return cmd -> (aliases, command table entry)
22 for each matching command.
22 for each matching command.
23 Return debug commands (or their aliases) only if no normal command matches.
23 Return debug commands (or their aliases) only if no normal command matches.
24 """
24 """
25 choice = {}
25 choice = {}
26 debugchoice = {}
26 debugchoice = {}
27
27
28 if cmd in table:
28 if cmd in table:
29 # short-circuit exact matches, "log" alias beats "^log|history"
29 # short-circuit exact matches, "log" alias beats "^log|history"
30 keys = [cmd]
30 keys = [cmd]
31 else:
31 else:
32 keys = table.keys()
32 keys = table.keys()
33
33
34 for e in keys:
34 for e in keys:
35 aliases = parsealiases(e)
35 aliases = parsealiases(e)
36 found = None
36 found = None
37 if cmd in aliases:
37 if cmd in aliases:
38 found = cmd
38 found = cmd
39 elif not strict:
39 elif not strict:
40 for a in aliases:
40 for a in aliases:
41 if a.startswith(cmd):
41 if a.startswith(cmd):
42 found = a
42 found = a
43 break
43 break
44 if found is not None:
44 if found is not None:
45 if aliases[0].startswith("debug") or found.startswith("debug"):
45 if aliases[0].startswith("debug") or found.startswith("debug"):
46 debugchoice[found] = (aliases, table[e])
46 debugchoice[found] = (aliases, table[e])
47 else:
47 else:
48 choice[found] = (aliases, table[e])
48 choice[found] = (aliases, table[e])
49
49
50 if not choice and debugchoice:
50 if not choice and debugchoice:
51 choice = debugchoice
51 choice = debugchoice
52
52
53 return choice
53 return choice
54
54
55 def findcmd(cmd, table, strict=True):
55 def findcmd(cmd, table, strict=True):
56 """Return (aliases, command table entry) for command string."""
56 """Return (aliases, command table entry) for command string."""
57 choice = findpossible(cmd, table, strict)
57 choice = findpossible(cmd, table, strict)
58
58
59 if cmd in choice:
59 if cmd in choice:
60 return choice[cmd]
60 return choice[cmd]
61
61
62 if len(choice) > 1:
62 if len(choice) > 1:
63 clist = choice.keys()
63 clist = choice.keys()
64 clist.sort()
64 clist.sort()
65 raise error.AmbiguousCommand(cmd, clist)
65 raise error.AmbiguousCommand(cmd, clist)
66
66
67 if choice:
67 if choice:
68 return choice.values()[0]
68 return choice.values()[0]
69
69
70 raise error.UnknownCommand(cmd)
70 raise error.UnknownCommand(cmd)
71
71
72 def findrepo(p):
72 def findrepo(p):
73 while not os.path.isdir(os.path.join(p, ".hg")):
73 while not os.path.isdir(os.path.join(p, ".hg")):
74 oldp, p = p, os.path.dirname(p)
74 oldp, p = p, os.path.dirname(p)
75 if p == oldp:
75 if p == oldp:
76 return None
76 return None
77
77
78 return p
78 return p
79
79
80 def bailifchanged(repo):
80 def bailifchanged(repo):
81 if repo.dirstate.p2() != nullid:
81 if repo.dirstate.p2() != nullid:
82 raise util.Abort(_('outstanding uncommitted merge'))
82 raise util.Abort(_('outstanding uncommitted merge'))
83 modified, added, removed, deleted = repo.status()[:4]
83 modified, added, removed, deleted = repo.status()[:4]
84 if modified or added or removed or deleted:
84 if modified or added or removed or deleted:
85 raise util.Abort(_("outstanding uncommitted changes"))
85 raise util.Abort(_("outstanding uncommitted changes"))
86 ctx = repo[None]
86 ctx = repo[None]
87 for s in ctx.substate:
87 for s in ctx.substate:
88 if ctx.sub(s).dirty():
88 if ctx.sub(s).dirty():
89 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
90
90
91 def logmessage(ui, opts):
91 def logmessage(ui, opts):
92 """ get the log message according to -m and -l option """
92 """ get the log message according to -m and -l option """
93 message = opts.get('message')
93 message = opts.get('message')
94 logfile = opts.get('logfile')
94 logfile = opts.get('logfile')
95
95
96 if message and logfile:
96 if message and logfile:
97 raise util.Abort(_('options --message and --logfile are mutually '
97 raise util.Abort(_('options --message and --logfile are mutually '
98 'exclusive'))
98 'exclusive'))
99 if not message and logfile:
99 if not message and logfile:
100 try:
100 try:
101 if logfile == '-':
101 if logfile == '-':
102 message = ui.fin.read()
102 message = ui.fin.read()
103 else:
103 else:
104 message = '\n'.join(util.readfile(logfile).splitlines())
104 message = '\n'.join(util.readfile(logfile).splitlines())
105 except IOError, inst:
105 except IOError, inst:
106 raise util.Abort(_("can't read commit message '%s': %s") %
106 raise util.Abort(_("can't read commit message '%s': %s") %
107 (logfile, inst.strerror))
107 (logfile, inst.strerror))
108 return message
108 return message
109
109
110 def loglimit(opts):
110 def loglimit(opts):
111 """get the log limit according to option -l/--limit"""
111 """get the log limit according to option -l/--limit"""
112 limit = opts.get('limit')
112 limit = opts.get('limit')
113 if limit:
113 if limit:
114 try:
114 try:
115 limit = int(limit)
115 limit = int(limit)
116 except ValueError:
116 except ValueError:
117 raise util.Abort(_('limit must be a positive integer'))
117 raise util.Abort(_('limit must be a positive integer'))
118 if limit <= 0:
118 if limit <= 0:
119 raise util.Abort(_('limit must be positive'))
119 raise util.Abort(_('limit must be positive'))
120 else:
120 else:
121 limit = None
121 limit = None
122 return limit
122 return limit
123
123
124 def makefilename(repo, pat, node, desc=None,
124 def makefilename(repo, pat, node, desc=None,
125 total=None, seqno=None, revwidth=None, pathname=None):
125 total=None, seqno=None, revwidth=None, pathname=None):
126 node_expander = {
126 node_expander = {
127 'H': lambda: hex(node),
127 'H': lambda: hex(node),
128 'R': lambda: str(repo.changelog.rev(node)),
128 'R': lambda: str(repo.changelog.rev(node)),
129 'h': lambda: short(node),
129 'h': lambda: short(node),
130 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 'm': lambda: re.sub('[^\w]', '_', str(desc))
131 }
131 }
132 expander = {
132 expander = {
133 '%': lambda: '%',
133 '%': lambda: '%',
134 'b': lambda: os.path.basename(repo.root),
134 'b': lambda: os.path.basename(repo.root),
135 }
135 }
136
136
137 try:
137 try:
138 if node:
138 if node:
139 expander.update(node_expander)
139 expander.update(node_expander)
140 if node:
140 if node:
141 expander['r'] = (lambda:
141 expander['r'] = (lambda:
142 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 str(repo.changelog.rev(node)).zfill(revwidth or 0))
143 if total is not None:
143 if total is not None:
144 expander['N'] = lambda: str(total)
144 expander['N'] = lambda: str(total)
145 if seqno is not None:
145 if seqno is not None:
146 expander['n'] = lambda: str(seqno)
146 expander['n'] = lambda: str(seqno)
147 if total is not None and seqno is not None:
147 if total is not None and seqno is not None:
148 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
149 if pathname is not None:
149 if pathname is not None:
150 expander['s'] = lambda: os.path.basename(pathname)
150 expander['s'] = lambda: os.path.basename(pathname)
151 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 expander['d'] = lambda: os.path.dirname(pathname) or '.'
152 expander['p'] = lambda: pathname
152 expander['p'] = lambda: pathname
153
153
154 newname = []
154 newname = []
155 patlen = len(pat)
155 patlen = len(pat)
156 i = 0
156 i = 0
157 while i < patlen:
157 while i < patlen:
158 c = pat[i]
158 c = pat[i]
159 if c == '%':
159 if c == '%':
160 i += 1
160 i += 1
161 c = pat[i]
161 c = pat[i]
162 c = expander[c]()
162 c = expander[c]()
163 newname.append(c)
163 newname.append(c)
164 i += 1
164 i += 1
165 return ''.join(newname)
165 return ''.join(newname)
166 except KeyError, inst:
166 except KeyError, inst:
167 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
168 inst.args[0])
168 inst.args[0])
169
169
170 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 def makefileobj(repo, pat, node=None, desc=None, total=None,
171 seqno=None, revwidth=None, mode='wb', pathname=None):
171 seqno=None, revwidth=None, mode='wb', pathname=None):
172
172
173 writable = mode not in ('r', 'rb')
173 writable = mode not in ('r', 'rb')
174
174
175 if not pat or pat == '-':
175 if not pat or pat == '-':
176 fp = writable and repo.ui.fout or repo.ui.fin
176 fp = writable and repo.ui.fout or repo.ui.fin
177 if util.safehasattr(fp, 'fileno'):
177 if util.safehasattr(fp, 'fileno'):
178 return os.fdopen(os.dup(fp.fileno()), mode)
178 return os.fdopen(os.dup(fp.fileno()), mode)
179 else:
179 else:
180 # if this fp can't be duped properly, return
180 # if this fp can't be duped properly, return
181 # a dummy object that can be closed
181 # a dummy object that can be closed
182 class wrappedfileobj(object):
182 class wrappedfileobj(object):
183 noop = lambda x: None
183 noop = lambda x: None
184 def __init__(self, f):
184 def __init__(self, f):
185 self.f = f
185 self.f = f
186 def __getattr__(self, attr):
186 def __getattr__(self, attr):
187 if attr == 'close':
187 if attr == 'close':
188 return self.noop
188 return self.noop
189 else:
189 else:
190 return getattr(self.f, attr)
190 return getattr(self.f, attr)
191
191
192 return wrappedfileobj(fp)
192 return wrappedfileobj(fp)
193 if util.safehasattr(pat, 'write') and writable:
193 if util.safehasattr(pat, 'write') and writable:
194 return pat
194 return pat
195 if util.safehasattr(pat, 'read') and 'r' in mode:
195 if util.safehasattr(pat, 'read') and 'r' in mode:
196 return pat
196 return pat
197 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
198 pathname),
198 pathname),
199 mode)
199 mode)
200
200
201 def openrevlog(repo, cmd, file_, opts):
201 def openrevlog(repo, cmd, file_, opts):
202 """opens the changelog, manifest, a filelog or a given revlog"""
202 """opens the changelog, manifest, a filelog or a given revlog"""
203 cl = opts['changelog']
203 cl = opts['changelog']
204 mf = opts['manifest']
204 mf = opts['manifest']
205 msg = None
205 msg = None
206 if cl and mf:
206 if cl and mf:
207 msg = _('cannot specify --changelog and --manifest at the same time')
207 msg = _('cannot specify --changelog and --manifest at the same time')
208 elif cl or mf:
208 elif cl or mf:
209 if file_:
209 if file_:
210 msg = _('cannot specify filename with --changelog or --manifest')
210 msg = _('cannot specify filename with --changelog or --manifest')
211 elif not repo:
211 elif not repo:
212 msg = _('cannot specify --changelog or --manifest '
212 msg = _('cannot specify --changelog or --manifest '
213 'without a repository')
213 'without a repository')
214 if msg:
214 if msg:
215 raise util.Abort(msg)
215 raise util.Abort(msg)
216
216
217 r = None
217 r = None
218 if repo:
218 if repo:
219 if cl:
219 if cl:
220 r = repo.changelog
220 r = repo.changelog
221 elif mf:
221 elif mf:
222 r = repo.manifest
222 r = repo.manifest
223 elif file_:
223 elif file_:
224 filelog = repo.file(file_)
224 filelog = repo.file(file_)
225 if len(filelog):
225 if len(filelog):
226 r = filelog
226 r = filelog
227 if not r:
227 if not r:
228 if not file_:
228 if not file_:
229 raise error.CommandError(cmd, _('invalid arguments'))
229 raise error.CommandError(cmd, _('invalid arguments'))
230 if not os.path.isfile(file_):
230 if not os.path.isfile(file_):
231 raise util.Abort(_("revlog '%s' not found") % file_)
231 raise util.Abort(_("revlog '%s' not found") % file_)
232 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
233 file_[:-2] + ".i")
233 file_[:-2] + ".i")
234 return r
234 return r
235
235
236 def copy(ui, repo, pats, opts, rename=False):
236 def copy(ui, repo, pats, opts, rename=False):
237 # called with the repo lock held
237 # called with the repo lock held
238 #
238 #
239 # hgsep => pathname that uses "/" to separate directories
239 # hgsep => pathname that uses "/" to separate directories
240 # ossep => pathname that uses os.sep to separate directories
240 # ossep => pathname that uses os.sep to separate directories
241 cwd = repo.getcwd()
241 cwd = repo.getcwd()
242 targets = {}
242 targets = {}
243 after = opts.get("after")
243 after = opts.get("after")
244 dryrun = opts.get("dry_run")
244 dryrun = opts.get("dry_run")
245 wctx = repo[None]
245 wctx = repo[None]
246
246
247 def walkpat(pat):
247 def walkpat(pat):
248 srcs = []
248 srcs = []
249 badstates = after and '?' or '?r'
249 badstates = after and '?' or '?r'
250 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 m = scmutil.match(repo[None], [pat], opts, globbed=True)
251 for abs in repo.walk(m):
251 for abs in repo.walk(m):
252 state = repo.dirstate[abs]
252 state = repo.dirstate[abs]
253 rel = m.rel(abs)
253 rel = m.rel(abs)
254 exact = m.exact(abs)
254 exact = m.exact(abs)
255 if state in badstates:
255 if state in badstates:
256 if exact and state == '?':
256 if exact and state == '?':
257 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 ui.warn(_('%s: not copying - file is not managed\n') % rel)
258 if exact and state == 'r':
258 if exact and state == 'r':
259 ui.warn(_('%s: not copying - file has been marked for'
259 ui.warn(_('%s: not copying - file has been marked for'
260 ' remove\n') % rel)
260 ' remove\n') % rel)
261 continue
261 continue
262 # abs: hgsep
262 # abs: hgsep
263 # rel: ossep
263 # rel: ossep
264 srcs.append((abs, rel, exact))
264 srcs.append((abs, rel, exact))
265 return srcs
265 return srcs
266
266
267 # abssrc: hgsep
267 # abssrc: hgsep
268 # relsrc: ossep
268 # relsrc: ossep
269 # otarget: ossep
269 # otarget: ossep
270 def copyfile(abssrc, relsrc, otarget, exact):
270 def copyfile(abssrc, relsrc, otarget, exact):
271 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
272 if '/' in abstarget:
272 if '/' in abstarget:
273 # We cannot normalize abstarget itself, this would prevent
273 # We cannot normalize abstarget itself, this would prevent
274 # case only renames, like a => A.
274 # case only renames, like a => A.
275 abspath, absname = abstarget.rsplit('/', 1)
275 abspath, absname = abstarget.rsplit('/', 1)
276 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
276 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
277 reltarget = repo.pathto(abstarget, cwd)
277 reltarget = repo.pathto(abstarget, cwd)
278 target = repo.wjoin(abstarget)
278 target = repo.wjoin(abstarget)
279 src = repo.wjoin(abssrc)
279 src = repo.wjoin(abssrc)
280 state = repo.dirstate[abstarget]
280 state = repo.dirstate[abstarget]
281
281
282 scmutil.checkportable(ui, abstarget)
282 scmutil.checkportable(ui, abstarget)
283
283
284 # check for collisions
284 # check for collisions
285 prevsrc = targets.get(abstarget)
285 prevsrc = targets.get(abstarget)
286 if prevsrc is not None:
286 if prevsrc is not None:
287 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
287 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
288 (reltarget, repo.pathto(abssrc, cwd),
288 (reltarget, repo.pathto(abssrc, cwd),
289 repo.pathto(prevsrc, cwd)))
289 repo.pathto(prevsrc, cwd)))
290 return
290 return
291
291
292 # check for overwrites
292 # check for overwrites
293 exists = os.path.lexists(target)
293 exists = os.path.lexists(target)
294 samefile = False
294 samefile = False
295 if exists and abssrc != abstarget:
295 if exists and abssrc != abstarget:
296 if (repo.dirstate.normalize(abssrc) ==
296 if (repo.dirstate.normalize(abssrc) ==
297 repo.dirstate.normalize(abstarget)):
297 repo.dirstate.normalize(abstarget)):
298 if not rename:
298 if not rename:
299 ui.warn(_("%s: can't copy - same file\n") % reltarget)
299 ui.warn(_("%s: can't copy - same file\n") % reltarget)
300 return
300 return
301 exists = False
301 exists = False
302 samefile = True
302 samefile = True
303
303
304 if not after and exists or after and state in 'mn':
304 if not after and exists or after and state in 'mn':
305 if not opts['force']:
305 if not opts['force']:
306 ui.warn(_('%s: not overwriting - file exists\n') %
306 ui.warn(_('%s: not overwriting - file exists\n') %
307 reltarget)
307 reltarget)
308 return
308 return
309
309
310 if after:
310 if after:
311 if not exists:
311 if not exists:
312 if rename:
312 if rename:
313 ui.warn(_('%s: not recording move - %s does not exist\n') %
313 ui.warn(_('%s: not recording move - %s does not exist\n') %
314 (relsrc, reltarget))
314 (relsrc, reltarget))
315 else:
315 else:
316 ui.warn(_('%s: not recording copy - %s does not exist\n') %
316 ui.warn(_('%s: not recording copy - %s does not exist\n') %
317 (relsrc, reltarget))
317 (relsrc, reltarget))
318 return
318 return
319 elif not dryrun:
319 elif not dryrun:
320 try:
320 try:
321 if exists:
321 if exists:
322 os.unlink(target)
322 os.unlink(target)
323 targetdir = os.path.dirname(target) or '.'
323 targetdir = os.path.dirname(target) or '.'
324 if not os.path.isdir(targetdir):
324 if not os.path.isdir(targetdir):
325 os.makedirs(targetdir)
325 os.makedirs(targetdir)
326 if samefile:
326 if samefile:
327 tmp = target + "~hgrename"
327 tmp = target + "~hgrename"
328 os.rename(src, tmp)
328 os.rename(src, tmp)
329 os.rename(tmp, target)
329 os.rename(tmp, target)
330 else:
330 else:
331 util.copyfile(src, target)
331 util.copyfile(src, target)
332 srcexists = True
332 srcexists = True
333 except IOError, inst:
333 except IOError, inst:
334 if inst.errno == errno.ENOENT:
334 if inst.errno == errno.ENOENT:
335 ui.warn(_('%s: deleted in working copy\n') % relsrc)
335 ui.warn(_('%s: deleted in working copy\n') % relsrc)
336 srcexists = False
336 srcexists = False
337 else:
337 else:
338 ui.warn(_('%s: cannot copy - %s\n') %
338 ui.warn(_('%s: cannot copy - %s\n') %
339 (relsrc, inst.strerror))
339 (relsrc, inst.strerror))
340 return True # report a failure
340 return True # report a failure
341
341
342 if ui.verbose or not exact:
342 if ui.verbose or not exact:
343 if rename:
343 if rename:
344 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
344 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
345 else:
345 else:
346 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
346 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
347
347
348 targets[abstarget] = abssrc
348 targets[abstarget] = abssrc
349
349
350 # fix up dirstate
350 # fix up dirstate
351 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
351 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
352 dryrun=dryrun, cwd=cwd)
352 dryrun=dryrun, cwd=cwd)
353 if rename and not dryrun:
353 if rename and not dryrun:
354 if not after and srcexists and not samefile:
354 if not after and srcexists and not samefile:
355 util.unlinkpath(repo.wjoin(abssrc))
355 util.unlinkpath(repo.wjoin(abssrc))
356 wctx.forget([abssrc])
356 wctx.forget([abssrc])
357
357
358 # pat: ossep
358 # pat: ossep
359 # dest ossep
359 # dest ossep
360 # srcs: list of (hgsep, hgsep, ossep, bool)
360 # srcs: list of (hgsep, hgsep, ossep, bool)
361 # return: function that takes hgsep and returns ossep
361 # return: function that takes hgsep and returns ossep
362 def targetpathfn(pat, dest, srcs):
362 def targetpathfn(pat, dest, srcs):
363 if os.path.isdir(pat):
363 if os.path.isdir(pat):
364 abspfx = scmutil.canonpath(repo.root, cwd, pat)
364 abspfx = scmutil.canonpath(repo.root, cwd, pat)
365 abspfx = util.localpath(abspfx)
365 abspfx = util.localpath(abspfx)
366 if destdirexists:
366 if destdirexists:
367 striplen = len(os.path.split(abspfx)[0])
367 striplen = len(os.path.split(abspfx)[0])
368 else:
368 else:
369 striplen = len(abspfx)
369 striplen = len(abspfx)
370 if striplen:
370 if striplen:
371 striplen += len(os.sep)
371 striplen += len(os.sep)
372 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
372 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
373 elif destdirexists:
373 elif destdirexists:
374 res = lambda p: os.path.join(dest,
374 res = lambda p: os.path.join(dest,
375 os.path.basename(util.localpath(p)))
375 os.path.basename(util.localpath(p)))
376 else:
376 else:
377 res = lambda p: dest
377 res = lambda p: dest
378 return res
378 return res
379
379
380 # pat: ossep
380 # pat: ossep
381 # dest ossep
381 # dest ossep
382 # srcs: list of (hgsep, hgsep, ossep, bool)
382 # srcs: list of (hgsep, hgsep, ossep, bool)
383 # return: function that takes hgsep and returns ossep
383 # return: function that takes hgsep and returns ossep
384 def targetpathafterfn(pat, dest, srcs):
384 def targetpathafterfn(pat, dest, srcs):
385 if matchmod.patkind(pat):
385 if matchmod.patkind(pat):
386 # a mercurial pattern
386 # a mercurial pattern
387 res = lambda p: os.path.join(dest,
387 res = lambda p: os.path.join(dest,
388 os.path.basename(util.localpath(p)))
388 os.path.basename(util.localpath(p)))
389 else:
389 else:
390 abspfx = scmutil.canonpath(repo.root, cwd, pat)
390 abspfx = scmutil.canonpath(repo.root, cwd, pat)
391 if len(abspfx) < len(srcs[0][0]):
391 if len(abspfx) < len(srcs[0][0]):
392 # A directory. Either the target path contains the last
392 # A directory. Either the target path contains the last
393 # component of the source path or it does not.
393 # component of the source path or it does not.
394 def evalpath(striplen):
394 def evalpath(striplen):
395 score = 0
395 score = 0
396 for s in srcs:
396 for s in srcs:
397 t = os.path.join(dest, util.localpath(s[0])[striplen:])
397 t = os.path.join(dest, util.localpath(s[0])[striplen:])
398 if os.path.lexists(t):
398 if os.path.lexists(t):
399 score += 1
399 score += 1
400 return score
400 return score
401
401
402 abspfx = util.localpath(abspfx)
402 abspfx = util.localpath(abspfx)
403 striplen = len(abspfx)
403 striplen = len(abspfx)
404 if striplen:
404 if striplen:
405 striplen += len(os.sep)
405 striplen += len(os.sep)
406 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
406 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
407 score = evalpath(striplen)
407 score = evalpath(striplen)
408 striplen1 = len(os.path.split(abspfx)[0])
408 striplen1 = len(os.path.split(abspfx)[0])
409 if striplen1:
409 if striplen1:
410 striplen1 += len(os.sep)
410 striplen1 += len(os.sep)
411 if evalpath(striplen1) > score:
411 if evalpath(striplen1) > score:
412 striplen = striplen1
412 striplen = striplen1
413 res = lambda p: os.path.join(dest,
413 res = lambda p: os.path.join(dest,
414 util.localpath(p)[striplen:])
414 util.localpath(p)[striplen:])
415 else:
415 else:
416 # a file
416 # a file
417 if destdirexists:
417 if destdirexists:
418 res = lambda p: os.path.join(dest,
418 res = lambda p: os.path.join(dest,
419 os.path.basename(util.localpath(p)))
419 os.path.basename(util.localpath(p)))
420 else:
420 else:
421 res = lambda p: dest
421 res = lambda p: dest
422 return res
422 return res
423
423
424
424
425 pats = scmutil.expandpats(pats)
425 pats = scmutil.expandpats(pats)
426 if not pats:
426 if not pats:
427 raise util.Abort(_('no source or destination specified'))
427 raise util.Abort(_('no source or destination specified'))
428 if len(pats) == 1:
428 if len(pats) == 1:
429 raise util.Abort(_('no destination specified'))
429 raise util.Abort(_('no destination specified'))
430 dest = pats.pop()
430 dest = pats.pop()
431 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
431 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
432 if not destdirexists:
432 if not destdirexists:
433 if len(pats) > 1 or matchmod.patkind(pats[0]):
433 if len(pats) > 1 or matchmod.patkind(pats[0]):
434 raise util.Abort(_('with multiple sources, destination must be an '
434 raise util.Abort(_('with multiple sources, destination must be an '
435 'existing directory'))
435 'existing directory'))
436 if util.endswithsep(dest):
436 if util.endswithsep(dest):
437 raise util.Abort(_('destination %s is not a directory') % dest)
437 raise util.Abort(_('destination %s is not a directory') % dest)
438
438
439 tfn = targetpathfn
439 tfn = targetpathfn
440 if after:
440 if after:
441 tfn = targetpathafterfn
441 tfn = targetpathafterfn
442 copylist = []
442 copylist = []
443 for pat in pats:
443 for pat in pats:
444 srcs = walkpat(pat)
444 srcs = walkpat(pat)
445 if not srcs:
445 if not srcs:
446 continue
446 continue
447 copylist.append((tfn(pat, dest, srcs), srcs))
447 copylist.append((tfn(pat, dest, srcs), srcs))
448 if not copylist:
448 if not copylist:
449 raise util.Abort(_('no files to copy'))
449 raise util.Abort(_('no files to copy'))
450
450
451 errors = 0
451 errors = 0
452 for targetpath, srcs in copylist:
452 for targetpath, srcs in copylist:
453 for abssrc, relsrc, exact in srcs:
453 for abssrc, relsrc, exact in srcs:
454 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
454 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
455 errors += 1
455 errors += 1
456
456
457 if errors:
457 if errors:
458 ui.warn(_('(consider using --after)\n'))
458 ui.warn(_('(consider using --after)\n'))
459
459
460 return errors != 0
460 return errors != 0
461
461
462 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
462 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
463 runargs=None, appendpid=False):
463 runargs=None, appendpid=False):
464 '''Run a command as a service.'''
464 '''Run a command as a service.'''
465
465
466 if opts['daemon'] and not opts['daemon_pipefds']:
466 if opts['daemon'] and not opts['daemon_pipefds']:
467 # Signal child process startup with file removal
467 # Signal child process startup with file removal
468 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
468 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
469 os.close(lockfd)
469 os.close(lockfd)
470 try:
470 try:
471 if not runargs:
471 if not runargs:
472 runargs = util.hgcmd() + sys.argv[1:]
472 runargs = util.hgcmd() + sys.argv[1:]
473 runargs.append('--daemon-pipefds=%s' % lockpath)
473 runargs.append('--daemon-pipefds=%s' % lockpath)
474 # Don't pass --cwd to the child process, because we've already
474 # Don't pass --cwd to the child process, because we've already
475 # changed directory.
475 # changed directory.
476 for i in xrange(1, len(runargs)):
476 for i in xrange(1, len(runargs)):
477 if runargs[i].startswith('--cwd='):
477 if runargs[i].startswith('--cwd='):
478 del runargs[i]
478 del runargs[i]
479 break
479 break
480 elif runargs[i].startswith('--cwd'):
480 elif runargs[i].startswith('--cwd'):
481 del runargs[i:i + 2]
481 del runargs[i:i + 2]
482 break
482 break
483 def condfn():
483 def condfn():
484 return not os.path.exists(lockpath)
484 return not os.path.exists(lockpath)
485 pid = util.rundetached(runargs, condfn)
485 pid = util.rundetached(runargs, condfn)
486 if pid < 0:
486 if pid < 0:
487 raise util.Abort(_('child process failed to start'))
487 raise util.Abort(_('child process failed to start'))
488 finally:
488 finally:
489 try:
489 try:
490 os.unlink(lockpath)
490 os.unlink(lockpath)
491 except OSError, e:
491 except OSError, e:
492 if e.errno != errno.ENOENT:
492 if e.errno != errno.ENOENT:
493 raise
493 raise
494 if parentfn:
494 if parentfn:
495 return parentfn(pid)
495 return parentfn(pid)
496 else:
496 else:
497 return
497 return
498
498
499 if initfn:
499 if initfn:
500 initfn()
500 initfn()
501
501
502 if opts['pid_file']:
502 if opts['pid_file']:
503 mode = appendpid and 'a' or 'w'
503 mode = appendpid and 'a' or 'w'
504 fp = open(opts['pid_file'], mode)
504 fp = open(opts['pid_file'], mode)
505 fp.write(str(os.getpid()) + '\n')
505 fp.write(str(os.getpid()) + '\n')
506 fp.close()
506 fp.close()
507
507
508 if opts['daemon_pipefds']:
508 if opts['daemon_pipefds']:
509 lockpath = opts['daemon_pipefds']
509 lockpath = opts['daemon_pipefds']
510 try:
510 try:
511 os.setsid()
511 os.setsid()
512 except AttributeError:
512 except AttributeError:
513 pass
513 pass
514 os.unlink(lockpath)
514 os.unlink(lockpath)
515 util.hidewindow()
515 util.hidewindow()
516 sys.stdout.flush()
516 sys.stdout.flush()
517 sys.stderr.flush()
517 sys.stderr.flush()
518
518
519 nullfd = os.open(os.devnull, os.O_RDWR)
519 nullfd = os.open(os.devnull, os.O_RDWR)
520 logfilefd = nullfd
520 logfilefd = nullfd
521 if logfile:
521 if logfile:
522 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
522 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
523 os.dup2(nullfd, 0)
523 os.dup2(nullfd, 0)
524 os.dup2(logfilefd, 1)
524 os.dup2(logfilefd, 1)
525 os.dup2(logfilefd, 2)
525 os.dup2(logfilefd, 2)
526 if nullfd not in (0, 1, 2):
526 if nullfd not in (0, 1, 2):
527 os.close(nullfd)
527 os.close(nullfd)
528 if logfile and logfilefd not in (0, 1, 2):
528 if logfile and logfilefd not in (0, 1, 2):
529 os.close(logfilefd)
529 os.close(logfilefd)
530
530
531 if runfn:
531 if runfn:
532 return runfn()
532 return runfn()
533
533
534 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
534 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
535 opts=None):
535 opts=None):
536 '''export changesets as hg patches.'''
536 '''export changesets as hg patches.'''
537
537
538 total = len(revs)
538 total = len(revs)
539 revwidth = max([len(str(rev)) for rev in revs])
539 revwidth = max([len(str(rev)) for rev in revs])
540
540
541 def single(rev, seqno, fp):
541 def single(rev, seqno, fp):
542 ctx = repo[rev]
542 ctx = repo[rev]
543 node = ctx.node()
543 node = ctx.node()
544 parents = [p.node() for p in ctx.parents() if p]
544 parents = [p.node() for p in ctx.parents() if p]
545 branch = ctx.branch()
545 branch = ctx.branch()
546 if switch_parent:
546 if switch_parent:
547 parents.reverse()
547 parents.reverse()
548 prev = (parents and parents[0]) or nullid
548 prev = (parents and parents[0]) or nullid
549
549
550 shouldclose = False
550 shouldclose = False
551 if not fp and len(template) > 0:
551 if not fp and len(template) > 0:
552 desc_lines = ctx.description().rstrip().split('\n')
552 desc_lines = ctx.description().rstrip().split('\n')
553 desc = desc_lines[0] #Commit always has a first line.
553 desc = desc_lines[0] #Commit always has a first line.
554 fp = makefileobj(repo, template, node, desc=desc, total=total,
554 fp = makefileobj(repo, template, node, desc=desc, total=total,
555 seqno=seqno, revwidth=revwidth, mode='ab')
555 seqno=seqno, revwidth=revwidth, mode='ab')
556 if fp != template:
556 if fp != template:
557 shouldclose = True
557 shouldclose = True
558 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
558 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
559 repo.ui.note("%s\n" % fp.name)
559 repo.ui.note("%s\n" % fp.name)
560
560
561 if not fp:
561 if not fp:
562 write = repo.ui.write
562 write = repo.ui.write
563 else:
563 else:
564 def write(s, **kw):
564 def write(s, **kw):
565 fp.write(s)
565 fp.write(s)
566
566
567
567
568 write("# HG changeset patch\n")
568 write("# HG changeset patch\n")
569 write("# User %s\n" % ctx.user())
569 write("# User %s\n" % ctx.user())
570 write("# Date %d %d\n" % ctx.date())
570 write("# Date %d %d\n" % ctx.date())
571 if branch and branch != 'default':
571 if branch and branch != 'default':
572 write("# Branch %s\n" % branch)
572 write("# Branch %s\n" % branch)
573 write("# Node ID %s\n" % hex(node))
573 write("# Node ID %s\n" % hex(node))
574 write("# Parent %s\n" % hex(prev))
574 write("# Parent %s\n" % hex(prev))
575 if len(parents) > 1:
575 if len(parents) > 1:
576 write("# Parent %s\n" % hex(parents[1]))
576 write("# Parent %s\n" % hex(parents[1]))
577 write(ctx.description().rstrip())
577 write(ctx.description().rstrip())
578 write("\n\n")
578 write("\n\n")
579
579
580 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
580 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
581 write(chunk, label=label)
581 write(chunk, label=label)
582
582
583 if shouldclose:
583 if shouldclose:
584 fp.close()
584 fp.close()
585
585
586 for seqno, rev in enumerate(revs):
586 for seqno, rev in enumerate(revs):
587 single(rev, seqno + 1, fp)
587 single(rev, seqno + 1, fp)
588
588
589 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
589 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
590 changes=None, stat=False, fp=None, prefix='',
590 changes=None, stat=False, fp=None, prefix='',
591 listsubrepos=False):
591 listsubrepos=False):
592 '''show diff or diffstat.'''
592 '''show diff or diffstat.'''
593 if fp is None:
593 if fp is None:
594 write = ui.write
594 write = ui.write
595 else:
595 else:
596 def write(s, **kw):
596 def write(s, **kw):
597 fp.write(s)
597 fp.write(s)
598
598
599 if stat:
599 if stat:
600 diffopts = diffopts.copy(context=0)
600 diffopts = diffopts.copy(context=0)
601 width = 80
601 width = 80
602 if not ui.plain():
602 if not ui.plain():
603 width = ui.termwidth()
603 width = ui.termwidth()
604 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
604 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
605 prefix=prefix)
605 prefix=prefix)
606 for chunk, label in patch.diffstatui(util.iterlines(chunks),
606 for chunk, label in patch.diffstatui(util.iterlines(chunks),
607 width=width,
607 width=width,
608 git=diffopts.git):
608 git=diffopts.git):
609 write(chunk, label=label)
609 write(chunk, label=label)
610 else:
610 else:
611 for chunk, label in patch.diffui(repo, node1, node2, match,
611 for chunk, label in patch.diffui(repo, node1, node2, match,
612 changes, diffopts, prefix=prefix):
612 changes, diffopts, prefix=prefix):
613 write(chunk, label=label)
613 write(chunk, label=label)
614
614
615 if listsubrepos:
615 if listsubrepos:
616 ctx1 = repo[node1]
616 ctx1 = repo[node1]
617 ctx2 = repo[node2]
617 ctx2 = repo[node2]
618 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
618 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
619 tempnode2 = node2
619 tempnode2 = node2
620 try:
620 try:
621 if node2 is not None:
621 if node2 is not None:
622 tempnode2 = ctx2.substate[subpath][1]
622 tempnode2 = ctx2.substate[subpath][1]
623 except KeyError:
623 except KeyError:
624 # A subrepo that existed in node1 was deleted between node1 and
624 # A subrepo that existed in node1 was deleted between node1 and
625 # node2 (inclusive). Thus, ctx2's substate won't contain that
625 # node2 (inclusive). Thus, ctx2's substate won't contain that
626 # subpath. The best we can do is to ignore it.
626 # subpath. The best we can do is to ignore it.
627 tempnode2 = None
627 tempnode2 = None
628 submatch = matchmod.narrowmatcher(subpath, match)
628 submatch = matchmod.narrowmatcher(subpath, match)
629 sub.diff(diffopts, tempnode2, submatch, changes=changes,
629 sub.diff(diffopts, tempnode2, submatch, changes=changes,
630 stat=stat, fp=fp, prefix=prefix)
630 stat=stat, fp=fp, prefix=prefix)
631
631
632 class changeset_printer(object):
632 class changeset_printer(object):
633 '''show changeset information when templating not requested.'''
633 '''show changeset information when templating not requested.'''
634
634
635 def __init__(self, ui, repo, patch, diffopts, buffered):
635 def __init__(self, ui, repo, patch, diffopts, buffered):
636 self.ui = ui
636 self.ui = ui
637 self.repo = repo
637 self.repo = repo
638 self.buffered = buffered
638 self.buffered = buffered
639 self.patch = patch
639 self.patch = patch
640 self.diffopts = diffopts
640 self.diffopts = diffopts
641 self.header = {}
641 self.header = {}
642 self.hunk = {}
642 self.hunk = {}
643 self.lastheader = None
643 self.lastheader = None
644 self.footer = None
644 self.footer = None
645
645
646 def flush(self, rev):
646 def flush(self, rev):
647 if rev in self.header:
647 if rev in self.header:
648 h = self.header[rev]
648 h = self.header[rev]
649 if h != self.lastheader:
649 if h != self.lastheader:
650 self.lastheader = h
650 self.lastheader = h
651 self.ui.write(h)
651 self.ui.write(h)
652 del self.header[rev]
652 del self.header[rev]
653 if rev in self.hunk:
653 if rev in self.hunk:
654 self.ui.write(self.hunk[rev])
654 self.ui.write(self.hunk[rev])
655 del self.hunk[rev]
655 del self.hunk[rev]
656 return 1
656 return 1
657 return 0
657 return 0
658
658
659 def close(self):
659 def close(self):
660 if self.footer:
660 if self.footer:
661 self.ui.write(self.footer)
661 self.ui.write(self.footer)
662
662
663 def show(self, ctx, copies=None, matchfn=None, **props):
663 def show(self, ctx, copies=None, matchfn=None, **props):
664 if self.buffered:
664 if self.buffered:
665 self.ui.pushbuffer()
665 self.ui.pushbuffer()
666 self._show(ctx, copies, matchfn, props)
666 self._show(ctx, copies, matchfn, props)
667 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
667 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
668 else:
668 else:
669 self._show(ctx, copies, matchfn, props)
669 self._show(ctx, copies, matchfn, props)
670
670
671 def _show(self, ctx, copies, matchfn, props):
671 def _show(self, ctx, copies, matchfn, props):
672 '''show a single changeset or file revision'''
672 '''show a single changeset or file revision'''
673 changenode = ctx.node()
673 changenode = ctx.node()
674 rev = ctx.rev()
674 rev = ctx.rev()
675
675
676 if self.ui.quiet:
676 if self.ui.quiet:
677 self.ui.write("%d:%s\n" % (rev, short(changenode)),
677 self.ui.write("%d:%s\n" % (rev, short(changenode)),
678 label='log.node')
678 label='log.node')
679 return
679 return
680
680
681 log = self.repo.changelog
681 log = self.repo.changelog
682 date = util.datestr(ctx.date())
682 date = util.datestr(ctx.date())
683
683
684 hexfunc = self.ui.debugflag and hex or short
684 hexfunc = self.ui.debugflag and hex or short
685
685
686 parents = [(p, hexfunc(log.node(p)))
686 parents = [(p, hexfunc(log.node(p)))
687 for p in self._meaningful_parentrevs(log, rev)]
687 for p in self._meaningful_parentrevs(log, rev)]
688
688
689 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
689 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
690 label='log.changeset')
690 label='log.changeset')
691
691
692 branch = ctx.branch()
692 branch = ctx.branch()
693 # don't show the default branch name
693 # don't show the default branch name
694 if branch != 'default':
694 if branch != 'default':
695 self.ui.write(_("branch: %s\n") % branch,
695 self.ui.write(_("branch: %s\n") % branch,
696 label='log.branch')
696 label='log.branch')
697 for bookmark in self.repo.nodebookmarks(changenode):
697 for bookmark in self.repo.nodebookmarks(changenode):
698 self.ui.write(_("bookmark: %s\n") % bookmark,
698 self.ui.write(_("bookmark: %s\n") % bookmark,
699 label='log.bookmark')
699 label='log.bookmark')
700 for tag in self.repo.nodetags(changenode):
700 for tag in self.repo.nodetags(changenode):
701 self.ui.write(_("tag: %s\n") % tag,
701 self.ui.write(_("tag: %s\n") % tag,
702 label='log.tag')
702 label='log.tag')
703 if self.ui.debugflag and ctx.phase():
703 if self.ui.debugflag and ctx.phase():
704 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
704 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
705 label='log.phase')
705 label='log.phase')
706 for parent in parents:
706 for parent in parents:
707 self.ui.write(_("parent: %d:%s\n") % parent,
707 self.ui.write(_("parent: %d:%s\n") % parent,
708 label='log.parent')
708 label='log.parent')
709
709
710 if self.ui.debugflag:
710 if self.ui.debugflag:
711 mnode = ctx.manifestnode()
711 mnode = ctx.manifestnode()
712 self.ui.write(_("manifest: %d:%s\n") %
712 self.ui.write(_("manifest: %d:%s\n") %
713 (self.repo.manifest.rev(mnode), hex(mnode)),
713 (self.repo.manifest.rev(mnode), hex(mnode)),
714 label='ui.debug log.manifest')
714 label='ui.debug log.manifest')
715 self.ui.write(_("user: %s\n") % ctx.user(),
715 self.ui.write(_("user: %s\n") % ctx.user(),
716 label='log.user')
716 label='log.user')
717 self.ui.write(_("date: %s\n") % date,
717 self.ui.write(_("date: %s\n") % date,
718 label='log.date')
718 label='log.date')
719
719
720 if self.ui.debugflag:
720 if self.ui.debugflag:
721 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
721 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
722 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
722 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
723 files):
723 files):
724 if value:
724 if value:
725 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
725 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
726 label='ui.debug log.files')
726 label='ui.debug log.files')
727 elif ctx.files() and self.ui.verbose:
727 elif ctx.files() and self.ui.verbose:
728 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
728 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
729 label='ui.note log.files')
729 label='ui.note log.files')
730 if copies and self.ui.verbose:
730 if copies and self.ui.verbose:
731 copies = ['%s (%s)' % c for c in copies]
731 copies = ['%s (%s)' % c for c in copies]
732 self.ui.write(_("copies: %s\n") % ' '.join(copies),
732 self.ui.write(_("copies: %s\n") % ' '.join(copies),
733 label='ui.note log.copies')
733 label='ui.note log.copies')
734
734
735 extra = ctx.extra()
735 extra = ctx.extra()
736 if extra and self.ui.debugflag:
736 if extra and self.ui.debugflag:
737 for key, value in sorted(extra.items()):
737 for key, value in sorted(extra.items()):
738 self.ui.write(_("extra: %s=%s\n")
738 self.ui.write(_("extra: %s=%s\n")
739 % (key, value.encode('string_escape')),
739 % (key, value.encode('string_escape')),
740 label='ui.debug log.extra')
740 label='ui.debug log.extra')
741
741
742 description = ctx.description().strip()
742 description = ctx.description().strip()
743 if description:
743 if description:
744 if self.ui.verbose:
744 if self.ui.verbose:
745 self.ui.write(_("description:\n"),
745 self.ui.write(_("description:\n"),
746 label='ui.note log.description')
746 label='ui.note log.description')
747 self.ui.write(description,
747 self.ui.write(description,
748 label='ui.note log.description')
748 label='ui.note log.description')
749 self.ui.write("\n\n")
749 self.ui.write("\n\n")
750 else:
750 else:
751 self.ui.write(_("summary: %s\n") %
751 self.ui.write(_("summary: %s\n") %
752 description.splitlines()[0],
752 description.splitlines()[0],
753 label='log.summary')
753 label='log.summary')
754 self.ui.write("\n")
754 self.ui.write("\n")
755
755
756 self.showpatch(changenode, matchfn)
756 self.showpatch(changenode, matchfn)
757
757
758 def showpatch(self, node, matchfn):
758 def showpatch(self, node, matchfn):
759 if not matchfn:
759 if not matchfn:
760 matchfn = self.patch
760 matchfn = self.patch
761 if matchfn:
761 if matchfn:
762 stat = self.diffopts.get('stat')
762 stat = self.diffopts.get('stat')
763 diff = self.diffopts.get('patch')
763 diff = self.diffopts.get('patch')
764 diffopts = patch.diffopts(self.ui, self.diffopts)
764 diffopts = patch.diffopts(self.ui, self.diffopts)
765 prev = self.repo.changelog.parents(node)[0]
765 prev = self.repo.changelog.parents(node)[0]
766 if stat:
766 if stat:
767 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
767 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
768 match=matchfn, stat=True)
768 match=matchfn, stat=True)
769 if diff:
769 if diff:
770 if stat:
770 if stat:
771 self.ui.write("\n")
771 self.ui.write("\n")
772 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
772 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
773 match=matchfn, stat=False)
773 match=matchfn, stat=False)
774 self.ui.write("\n")
774 self.ui.write("\n")
775
775
776 def _meaningful_parentrevs(self, log, rev):
776 def _meaningful_parentrevs(self, log, rev):
777 """Return list of meaningful (or all if debug) parentrevs for rev.
777 """Return list of meaningful (or all if debug) parentrevs for rev.
778
778
779 For merges (two non-nullrev revisions) both parents are meaningful.
779 For merges (two non-nullrev revisions) both parents are meaningful.
780 Otherwise the first parent revision is considered meaningful if it
780 Otherwise the first parent revision is considered meaningful if it
781 is not the preceding revision.
781 is not the preceding revision.
782 """
782 """
783 parents = log.parentrevs(rev)
783 parents = log.parentrevs(rev)
784 if not self.ui.debugflag and parents[1] == nullrev:
784 if not self.ui.debugflag and parents[1] == nullrev:
785 if parents[0] >= rev - 1:
785 if parents[0] >= rev - 1:
786 parents = []
786 parents = []
787 else:
787 else:
788 parents = [parents[0]]
788 parents = [parents[0]]
789 return parents
789 return parents
790
790
791
791
792 class changeset_templater(changeset_printer):
792 class changeset_templater(changeset_printer):
793 '''format changeset information.'''
793 '''format changeset information.'''
794
794
795 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
795 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
796 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
796 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
797 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
797 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
798 defaulttempl = {
798 defaulttempl = {
799 'parent': '{rev}:{node|formatnode} ',
799 'parent': '{rev}:{node|formatnode} ',
800 'manifest': '{rev}:{node|formatnode}',
800 'manifest': '{rev}:{node|formatnode}',
801 'file_copy': '{name} ({source})',
801 'file_copy': '{name} ({source})',
802 'extra': '{key}={value|stringescape}'
802 'extra': '{key}={value|stringescape}'
803 }
803 }
804 # filecopy is preserved for compatibility reasons
804 # filecopy is preserved for compatibility reasons
805 defaulttempl['filecopy'] = defaulttempl['file_copy']
805 defaulttempl['filecopy'] = defaulttempl['file_copy']
806 self.t = templater.templater(mapfile, {'formatnode': formatnode},
806 self.t = templater.templater(mapfile, {'formatnode': formatnode},
807 cache=defaulttempl)
807 cache=defaulttempl)
808 self.cache = {}
808 self.cache = {}
809
809
810 def use_template(self, t):
810 def use_template(self, t):
811 '''set template string to use'''
811 '''set template string to use'''
812 self.t.cache['changeset'] = t
812 self.t.cache['changeset'] = t
813
813
814 def _meaningful_parentrevs(self, ctx):
814 def _meaningful_parentrevs(self, ctx):
815 """Return list of meaningful (or all if debug) parentrevs for rev.
815 """Return list of meaningful (or all if debug) parentrevs for rev.
816 """
816 """
817 parents = ctx.parents()
817 parents = ctx.parents()
818 if len(parents) > 1:
818 if len(parents) > 1:
819 return parents
819 return parents
820 if self.ui.debugflag:
820 if self.ui.debugflag:
821 return [parents[0], self.repo['null']]
821 return [parents[0], self.repo['null']]
822 if parents[0].rev() >= ctx.rev() - 1:
822 if parents[0].rev() >= ctx.rev() - 1:
823 return []
823 return []
824 return parents
824 return parents
825
825
826 def _show(self, ctx, copies, matchfn, props):
826 def _show(self, ctx, copies, matchfn, props):
827 '''show a single changeset or file revision'''
827 '''show a single changeset or file revision'''
828
828
829 showlist = templatekw.showlist
829 showlist = templatekw.showlist
830
830
831 # showparents() behaviour depends on ui trace level which
831 # showparents() behaviour depends on ui trace level which
832 # causes unexpected behaviours at templating level and makes
832 # causes unexpected behaviours at templating level and makes
833 # it harder to extract it in a standalone function. Its
833 # it harder to extract it in a standalone function. Its
834 # behaviour cannot be changed so leave it here for now.
834 # behaviour cannot be changed so leave it here for now.
835 def showparents(**args):
835 def showparents(**args):
836 ctx = args['ctx']
836 ctx = args['ctx']
837 parents = [[('rev', p.rev()), ('node', p.hex())]
837 parents = [[('rev', p.rev()), ('node', p.hex())]
838 for p in self._meaningful_parentrevs(ctx)]
838 for p in self._meaningful_parentrevs(ctx)]
839 return showlist('parent', parents, **args)
839 return showlist('parent', parents, **args)
840
840
841 props = props.copy()
841 props = props.copy()
842 props.update(templatekw.keywords)
842 props.update(templatekw.keywords)
843 props['parents'] = showparents
843 props['parents'] = showparents
844 props['templ'] = self.t
844 props['templ'] = self.t
845 props['ctx'] = ctx
845 props['ctx'] = ctx
846 props['repo'] = self.repo
846 props['repo'] = self.repo
847 props['revcache'] = {'copies': copies}
847 props['revcache'] = {'copies': copies}
848 props['cache'] = self.cache
848 props['cache'] = self.cache
849
849
850 # find correct templates for current mode
850 # find correct templates for current mode
851
851
852 tmplmodes = [
852 tmplmodes = [
853 (True, None),
853 (True, None),
854 (self.ui.verbose, 'verbose'),
854 (self.ui.verbose, 'verbose'),
855 (self.ui.quiet, 'quiet'),
855 (self.ui.quiet, 'quiet'),
856 (self.ui.debugflag, 'debug'),
856 (self.ui.debugflag, 'debug'),
857 ]
857 ]
858
858
859 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
859 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
860 for mode, postfix in tmplmodes:
860 for mode, postfix in tmplmodes:
861 for type in types:
861 for type in types:
862 cur = postfix and ('%s_%s' % (type, postfix)) or type
862 cur = postfix and ('%s_%s' % (type, postfix)) or type
863 if mode and cur in self.t:
863 if mode and cur in self.t:
864 types[type] = cur
864 types[type] = cur
865
865
866 try:
866 try:
867
867
868 # write header
868 # write header
869 if types['header']:
869 if types['header']:
870 h = templater.stringify(self.t(types['header'], **props))
870 h = templater.stringify(self.t(types['header'], **props))
871 if self.buffered:
871 if self.buffered:
872 self.header[ctx.rev()] = h
872 self.header[ctx.rev()] = h
873 else:
873 else:
874 if self.lastheader != h:
874 if self.lastheader != h:
875 self.lastheader = h
875 self.lastheader = h
876 self.ui.write(h)
876 self.ui.write(h)
877
877
878 # write changeset metadata, then patch if requested
878 # write changeset metadata, then patch if requested
879 key = types['changeset']
879 key = types['changeset']
880 self.ui.write(templater.stringify(self.t(key, **props)))
880 self.ui.write(templater.stringify(self.t(key, **props)))
881 self.showpatch(ctx.node(), matchfn)
881 self.showpatch(ctx.node(), matchfn)
882
882
883 if types['footer']:
883 if types['footer']:
884 if not self.footer:
884 if not self.footer:
885 self.footer = templater.stringify(self.t(types['footer'],
885 self.footer = templater.stringify(self.t(types['footer'],
886 **props))
886 **props))
887
887
888 except KeyError, inst:
888 except KeyError, inst:
889 msg = _("%s: no key named '%s'")
889 msg = _("%s: no key named '%s'")
890 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
890 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
891 except SyntaxError, inst:
891 except SyntaxError, inst:
892 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
892 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
893
893
894 def show_changeset(ui, repo, opts, buffered=False):
894 def show_changeset(ui, repo, opts, buffered=False):
895 """show one changeset using template or regular display.
895 """show one changeset using template or regular display.
896
896
897 Display format will be the first non-empty hit of:
897 Display format will be the first non-empty hit of:
898 1. option 'template'
898 1. option 'template'
899 2. option 'style'
899 2. option 'style'
900 3. [ui] setting 'logtemplate'
900 3. [ui] setting 'logtemplate'
901 4. [ui] setting 'style'
901 4. [ui] setting 'style'
902 If all of these values are either the unset or the empty string,
902 If all of these values are either the unset or the empty string,
903 regular display via changeset_printer() is done.
903 regular display via changeset_printer() is done.
904 """
904 """
905 # options
905 # options
906 patch = False
906 patch = False
907 if opts.get('patch') or opts.get('stat'):
907 if opts.get('patch') or opts.get('stat'):
908 patch = scmutil.matchall(repo)
908 patch = scmutil.matchall(repo)
909
909
910 tmpl = opts.get('template')
910 tmpl = opts.get('template')
911 style = None
911 style = None
912 if tmpl:
912 if tmpl:
913 tmpl = templater.parsestring(tmpl, quoted=False)
913 tmpl = templater.parsestring(tmpl, quoted=False)
914 else:
914 else:
915 style = opts.get('style')
915 style = opts.get('style')
916
916
917 # ui settings
917 # ui settings
918 if not (tmpl or style):
918 if not (tmpl or style):
919 tmpl = ui.config('ui', 'logtemplate')
919 tmpl = ui.config('ui', 'logtemplate')
920 if tmpl:
920 if tmpl:
921 try:
921 try:
922 tmpl = templater.parsestring(tmpl)
922 tmpl = templater.parsestring(tmpl)
923 except SyntaxError:
923 except SyntaxError:
924 tmpl = templater.parsestring(tmpl, quoted=False)
924 tmpl = templater.parsestring(tmpl, quoted=False)
925 else:
925 else:
926 style = util.expandpath(ui.config('ui', 'style', ''))
926 style = util.expandpath(ui.config('ui', 'style', ''))
927
927
928 if not (tmpl or style):
928 if not (tmpl or style):
929 return changeset_printer(ui, repo, patch, opts, buffered)
929 return changeset_printer(ui, repo, patch, opts, buffered)
930
930
931 mapfile = None
931 mapfile = None
932 if style and not tmpl:
932 if style and not tmpl:
933 mapfile = style
933 mapfile = style
934 if not os.path.split(mapfile)[0]:
934 if not os.path.split(mapfile)[0]:
935 mapname = (templater.templatepath('map-cmdline.' + mapfile)
935 mapname = (templater.templatepath('map-cmdline.' + mapfile)
936 or templater.templatepath(mapfile))
936 or templater.templatepath(mapfile))
937 if mapname:
937 if mapname:
938 mapfile = mapname
938 mapfile = mapname
939
939
940 try:
940 try:
941 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
941 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
942 except SyntaxError, inst:
942 except SyntaxError, inst:
943 raise util.Abort(inst.args[0])
943 raise util.Abort(inst.args[0])
944 if tmpl:
944 if tmpl:
945 t.use_template(tmpl)
945 t.use_template(tmpl)
946 return t
946 return t
947
947
948 def finddate(ui, repo, date):
948 def finddate(ui, repo, date):
949 """Find the tipmost changeset that matches the given date spec"""
949 """Find the tipmost changeset that matches the given date spec"""
950
950
951 df = util.matchdate(date)
951 df = util.matchdate(date)
952 m = scmutil.matchall(repo)
952 m = scmutil.matchall(repo)
953 results = {}
953 results = {}
954
954
955 def prep(ctx, fns):
955 def prep(ctx, fns):
956 d = ctx.date()
956 d = ctx.date()
957 if df(d[0]):
957 if df(d[0]):
958 results[ctx.rev()] = d
958 results[ctx.rev()] = d
959
959
960 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
960 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
961 rev = ctx.rev()
961 rev = ctx.rev()
962 if rev in results:
962 if rev in results:
963 ui.status(_("found revision %s from %s\n") %
963 ui.status(_("found revision %s from %s\n") %
964 (rev, util.datestr(results[rev])))
964 (rev, util.datestr(results[rev])))
965 return str(rev)
965 return str(rev)
966
966
967 raise util.Abort(_("revision matching date not found"))
967 raise util.Abort(_("revision matching date not found"))
968
968
969 def increasingwindows(start, end, windowsize=8, sizelimit=512):
969 def increasingwindows(start, end, windowsize=8, sizelimit=512):
970 if start < end:
970 if start < end:
971 while start < end:
971 while start < end:
972 yield start, min(windowsize, end - start)
972 yield start, min(windowsize, end - start)
973 start += windowsize
973 start += windowsize
974 if windowsize < sizelimit:
974 if windowsize < sizelimit:
975 windowsize *= 2
975 windowsize *= 2
976 else:
976 else:
977 while start > end:
977 while start > end:
978 yield start, min(windowsize, start - end - 1)
978 yield start, min(windowsize, start - end - 1)
979 start -= windowsize
979 start -= windowsize
980 if windowsize < sizelimit:
980 if windowsize < sizelimit:
981 windowsize *= 2
981 windowsize *= 2
982
982
983 def walkchangerevs(repo, match, opts, prepare):
983 def walkchangerevs(repo, match, opts, prepare):
984 '''Iterate over files and the revs in which they changed.
984 '''Iterate over files and the revs in which they changed.
985
985
986 Callers most commonly need to iterate backwards over the history
986 Callers most commonly need to iterate backwards over the history
987 in which they are interested. Doing so has awful (quadratic-looking)
987 in which they are interested. Doing so has awful (quadratic-looking)
988 performance, so we use iterators in a "windowed" way.
988 performance, so we use iterators in a "windowed" way.
989
989
990 We walk a window of revisions in the desired order. Within the
990 We walk a window of revisions in the desired order. Within the
991 window, we first walk forwards to gather data, then in the desired
991 window, we first walk forwards to gather data, then in the desired
992 order (usually backwards) to display it.
992 order (usually backwards) to display it.
993
993
994 This function returns an iterator yielding contexts. Before
994 This function returns an iterator yielding contexts. Before
995 yielding each context, the iterator will first call the prepare
995 yielding each context, the iterator will first call the prepare
996 function on each context in the window in forward order.'''
996 function on each context in the window in forward order.'''
997
997
998 follow = opts.get('follow') or opts.get('follow_first')
998 follow = opts.get('follow') or opts.get('follow_first')
999
999
1000 if not len(repo):
1000 if not len(repo):
1001 return []
1001 return []
1002
1002 if opts.get('rev'):
1003 if follow:
1003 revs = scmutil.revrange(repo, opts.get('rev'))
1004 defrange = '%s:0' % repo['.'].rev()
1004 elif follow:
1005 revs = repo.revs('reverse(:.)')
1005 else:
1006 else:
1006 defrange = '-1:0'
1007 revs = list(repo)
1007 revs = scmutil.revrange(repo, opts.get('rev') or [defrange])
1008 revs.reverse()
1008 if not revs:
1009 if not revs:
1009 return []
1010 return []
1010 wanted = set()
1011 wanted = set()
1011 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1012 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1012 fncache = {}
1013 fncache = {}
1013 change = repo.changectx
1014 change = repo.changectx
1014
1015
1015 # First step is to fill wanted, the set of revisions that we want to yield.
1016 # First step is to fill wanted, the set of revisions that we want to yield.
1016 # When it does not induce extra cost, we also fill fncache for revisions in
1017 # When it does not induce extra cost, we also fill fncache for revisions in
1017 # wanted: a cache of filenames that were changed (ctx.files()) and that
1018 # wanted: a cache of filenames that were changed (ctx.files()) and that
1018 # match the file filtering conditions.
1019 # match the file filtering conditions.
1019
1020
1020 if not slowpath and not match.files():
1021 if not slowpath and not match.files():
1021 # No files, no patterns. Display all revs.
1022 # No files, no patterns. Display all revs.
1022 wanted = set(revs)
1023 wanted = set(revs)
1023 copies = []
1024 copies = []
1024
1025
1025 if not slowpath and match.files():
1026 if not slowpath and match.files():
1026 # We only have to read through the filelog to find wanted revisions
1027 # We only have to read through the filelog to find wanted revisions
1027
1028
1028 minrev, maxrev = min(revs), max(revs)
1029 minrev, maxrev = min(revs), max(revs)
1029 def filerevgen(filelog, last):
1030 def filerevgen(filelog, last):
1030 """
1031 """
1031 Only files, no patterns. Check the history of each file.
1032 Only files, no patterns. Check the history of each file.
1032
1033
1033 Examines filelog entries within minrev, maxrev linkrev range
1034 Examines filelog entries within minrev, maxrev linkrev range
1034 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1035 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1035 tuples in backwards order
1036 tuples in backwards order
1036 """
1037 """
1037 cl_count = len(repo)
1038 cl_count = len(repo)
1038 revs = []
1039 revs = []
1039 for j in xrange(0, last + 1):
1040 for j in xrange(0, last + 1):
1040 linkrev = filelog.linkrev(j)
1041 linkrev = filelog.linkrev(j)
1041 if linkrev < minrev:
1042 if linkrev < minrev:
1042 continue
1043 continue
1043 # only yield rev for which we have the changelog, it can
1044 # only yield rev for which we have the changelog, it can
1044 # happen while doing "hg log" during a pull or commit
1045 # happen while doing "hg log" during a pull or commit
1045 if linkrev >= cl_count:
1046 if linkrev >= cl_count:
1046 break
1047 break
1047
1048
1048 parentlinkrevs = []
1049 parentlinkrevs = []
1049 for p in filelog.parentrevs(j):
1050 for p in filelog.parentrevs(j):
1050 if p != nullrev:
1051 if p != nullrev:
1051 parentlinkrevs.append(filelog.linkrev(p))
1052 parentlinkrevs.append(filelog.linkrev(p))
1052 n = filelog.node(j)
1053 n = filelog.node(j)
1053 revs.append((linkrev, parentlinkrevs,
1054 revs.append((linkrev, parentlinkrevs,
1054 follow and filelog.renamed(n)))
1055 follow and filelog.renamed(n)))
1055
1056
1056 return reversed(revs)
1057 return reversed(revs)
1057 def iterfiles():
1058 def iterfiles():
1058 pctx = repo['.']
1059 pctx = repo['.']
1059 for filename in match.files():
1060 for filename in match.files():
1060 if follow:
1061 if follow:
1061 if filename not in pctx:
1062 if filename not in pctx:
1062 raise util.Abort(_('cannot follow file not in parent '
1063 raise util.Abort(_('cannot follow file not in parent '
1063 'revision: "%s"') % filename)
1064 'revision: "%s"') % filename)
1064 yield filename, pctx[filename].filenode()
1065 yield filename, pctx[filename].filenode()
1065 else:
1066 else:
1066 yield filename, None
1067 yield filename, None
1067 for filename_node in copies:
1068 for filename_node in copies:
1068 yield filename_node
1069 yield filename_node
1069 for file_, node in iterfiles():
1070 for file_, node in iterfiles():
1070 filelog = repo.file(file_)
1071 filelog = repo.file(file_)
1071 if not len(filelog):
1072 if not len(filelog):
1072 if node is None:
1073 if node is None:
1073 # A zero count may be a directory or deleted file, so
1074 # A zero count may be a directory or deleted file, so
1074 # try to find matching entries on the slow path.
1075 # try to find matching entries on the slow path.
1075 if follow:
1076 if follow:
1076 raise util.Abort(
1077 raise util.Abort(
1077 _('cannot follow nonexistent file: "%s"') % file_)
1078 _('cannot follow nonexistent file: "%s"') % file_)
1078 slowpath = True
1079 slowpath = True
1079 break
1080 break
1080 else:
1081 else:
1081 continue
1082 continue
1082
1083
1083 if node is None:
1084 if node is None:
1084 last = len(filelog) - 1
1085 last = len(filelog) - 1
1085 else:
1086 else:
1086 last = filelog.rev(node)
1087 last = filelog.rev(node)
1087
1088
1088
1089
1089 # keep track of all ancestors of the file
1090 # keep track of all ancestors of the file
1090 ancestors = set([filelog.linkrev(last)])
1091 ancestors = set([filelog.linkrev(last)])
1091
1092
1092 # iterate from latest to oldest revision
1093 # iterate from latest to oldest revision
1093 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1094 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1094 if not follow:
1095 if not follow:
1095 if rev > maxrev:
1096 if rev > maxrev:
1096 continue
1097 continue
1097 else:
1098 else:
1098 # Note that last might not be the first interesting
1099 # Note that last might not be the first interesting
1099 # rev to us:
1100 # rev to us:
1100 # if the file has been changed after maxrev, we'll
1101 # if the file has been changed after maxrev, we'll
1101 # have linkrev(last) > maxrev, and we still need
1102 # have linkrev(last) > maxrev, and we still need
1102 # to explore the file graph
1103 # to explore the file graph
1103 if rev not in ancestors:
1104 if rev not in ancestors:
1104 continue
1105 continue
1105 # XXX insert 1327 fix here
1106 # XXX insert 1327 fix here
1106 if flparentlinkrevs:
1107 if flparentlinkrevs:
1107 ancestors.update(flparentlinkrevs)
1108 ancestors.update(flparentlinkrevs)
1108
1109
1109 fncache.setdefault(rev, []).append(file_)
1110 fncache.setdefault(rev, []).append(file_)
1110 wanted.add(rev)
1111 wanted.add(rev)
1111 if copied:
1112 if copied:
1112 copies.append(copied)
1113 copies.append(copied)
1113 if slowpath:
1114 if slowpath:
1114 # We have to read the changelog to match filenames against
1115 # We have to read the changelog to match filenames against
1115 # changed files
1116 # changed files
1116
1117
1117 if follow:
1118 if follow:
1118 raise util.Abort(_('can only follow copies/renames for explicit '
1119 raise util.Abort(_('can only follow copies/renames for explicit '
1119 'filenames'))
1120 'filenames'))
1120
1121
1121 # The slow path checks files modified in every changeset.
1122 # The slow path checks files modified in every changeset.
1122 for i in sorted(revs):
1123 for i in sorted(revs):
1123 ctx = change(i)
1124 ctx = change(i)
1124 matches = filter(match, ctx.files())
1125 matches = filter(match, ctx.files())
1125 if matches:
1126 if matches:
1126 fncache[i] = matches
1127 fncache[i] = matches
1127 wanted.add(i)
1128 wanted.add(i)
1128
1129
1129 class followfilter(object):
1130 class followfilter(object):
1130 def __init__(self, onlyfirst=False):
1131 def __init__(self, onlyfirst=False):
1131 self.startrev = nullrev
1132 self.startrev = nullrev
1132 self.roots = set()
1133 self.roots = set()
1133 self.onlyfirst = onlyfirst
1134 self.onlyfirst = onlyfirst
1134
1135
1135 def match(self, rev):
1136 def match(self, rev):
1136 def realparents(rev):
1137 def realparents(rev):
1137 if self.onlyfirst:
1138 if self.onlyfirst:
1138 return repo.changelog.parentrevs(rev)[0:1]
1139 return repo.changelog.parentrevs(rev)[0:1]
1139 else:
1140 else:
1140 return filter(lambda x: x != nullrev,
1141 return filter(lambda x: x != nullrev,
1141 repo.changelog.parentrevs(rev))
1142 repo.changelog.parentrevs(rev))
1142
1143
1143 if self.startrev == nullrev:
1144 if self.startrev == nullrev:
1144 self.startrev = rev
1145 self.startrev = rev
1145 return True
1146 return True
1146
1147
1147 if rev > self.startrev:
1148 if rev > self.startrev:
1148 # forward: all descendants
1149 # forward: all descendants
1149 if not self.roots:
1150 if not self.roots:
1150 self.roots.add(self.startrev)
1151 self.roots.add(self.startrev)
1151 for parent in realparents(rev):
1152 for parent in realparents(rev):
1152 if parent in self.roots:
1153 if parent in self.roots:
1153 self.roots.add(rev)
1154 self.roots.add(rev)
1154 return True
1155 return True
1155 else:
1156 else:
1156 # backwards: all parents
1157 # backwards: all parents
1157 if not self.roots:
1158 if not self.roots:
1158 self.roots.update(realparents(self.startrev))
1159 self.roots.update(realparents(self.startrev))
1159 if rev in self.roots:
1160 if rev in self.roots:
1160 self.roots.remove(rev)
1161 self.roots.remove(rev)
1161 self.roots.update(realparents(rev))
1162 self.roots.update(realparents(rev))
1162 return True
1163 return True
1163
1164
1164 return False
1165 return False
1165
1166
1166 # it might be worthwhile to do this in the iterator if the rev range
1167 # it might be worthwhile to do this in the iterator if the rev range
1167 # is descending and the prune args are all within that range
1168 # is descending and the prune args are all within that range
1168 for rev in opts.get('prune', ()):
1169 for rev in opts.get('prune', ()):
1169 rev = repo[rev].rev()
1170 rev = repo[rev].rev()
1170 ff = followfilter()
1171 ff = followfilter()
1171 stop = min(revs[0], revs[-1])
1172 stop = min(revs[0], revs[-1])
1172 for x in xrange(rev, stop - 1, -1):
1173 for x in xrange(rev, stop - 1, -1):
1173 if ff.match(x):
1174 if ff.match(x):
1174 wanted.discard(x)
1175 wanted.discard(x)
1175
1176
1176 # Now that wanted is correctly initialized, we can iterate over the
1177 # Now that wanted is correctly initialized, we can iterate over the
1177 # revision range, yielding only revisions in wanted.
1178 # revision range, yielding only revisions in wanted.
1178 def iterate():
1179 def iterate():
1179 if follow and not match.files():
1180 if follow and not match.files():
1180 ff = followfilter(onlyfirst=opts.get('follow_first'))
1181 ff = followfilter(onlyfirst=opts.get('follow_first'))
1181 def want(rev):
1182 def want(rev):
1182 return ff.match(rev) and rev in wanted
1183 return ff.match(rev) and rev in wanted
1183 else:
1184 else:
1184 def want(rev):
1185 def want(rev):
1185 return rev in wanted
1186 return rev in wanted
1186
1187
1187 for i, window in increasingwindows(0, len(revs)):
1188 for i, window in increasingwindows(0, len(revs)):
1188 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1189 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1189 for rev in sorted(nrevs):
1190 for rev in sorted(nrevs):
1190 fns = fncache.get(rev)
1191 fns = fncache.get(rev)
1191 ctx = change(rev)
1192 ctx = change(rev)
1192 if not fns:
1193 if not fns:
1193 def fns_generator():
1194 def fns_generator():
1194 for f in ctx.files():
1195 for f in ctx.files():
1195 if match(f):
1196 if match(f):
1196 yield f
1197 yield f
1197 fns = fns_generator()
1198 fns = fns_generator()
1198 prepare(ctx, fns)
1199 prepare(ctx, fns)
1199 for rev in nrevs:
1200 for rev in nrevs:
1200 yield change(rev)
1201 yield change(rev)
1201 return iterate()
1202 return iterate()
1202
1203
1203 def _makegraphfilematcher(repo, pats, followfirst):
1204 def _makegraphfilematcher(repo, pats, followfirst):
1204 # When displaying a revision with --patch --follow FILE, we have
1205 # When displaying a revision with --patch --follow FILE, we have
1205 # to know which file of the revision must be diffed. With
1206 # to know which file of the revision must be diffed. With
1206 # --follow, we want the names of the ancestors of FILE in the
1207 # --follow, we want the names of the ancestors of FILE in the
1207 # revision, stored in "fcache". "fcache" is populated by
1208 # revision, stored in "fcache". "fcache" is populated by
1208 # reproducing the graph traversal already done by --follow revset
1209 # reproducing the graph traversal already done by --follow revset
1209 # and relating linkrevs to file names (which is not "correct" but
1210 # and relating linkrevs to file names (which is not "correct" but
1210 # good enough).
1211 # good enough).
1211 fcache = {}
1212 fcache = {}
1212 fcacheready = [False]
1213 fcacheready = [False]
1213 pctx = repo['.']
1214 pctx = repo['.']
1214 wctx = repo[None]
1215 wctx = repo[None]
1215
1216
1216 def populate():
1217 def populate():
1217 for fn in pats:
1218 for fn in pats:
1218 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1219 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1219 for c in i:
1220 for c in i:
1220 fcache.setdefault(c.linkrev(), set()).add(c.path())
1221 fcache.setdefault(c.linkrev(), set()).add(c.path())
1221
1222
1222 def filematcher(rev):
1223 def filematcher(rev):
1223 if not fcacheready[0]:
1224 if not fcacheready[0]:
1224 # Lazy initialization
1225 # Lazy initialization
1225 fcacheready[0] = True
1226 fcacheready[0] = True
1226 populate()
1227 populate()
1227 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1228 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1228
1229
1229 return filematcher
1230 return filematcher
1230
1231
1231 def _makegraphlogrevset(repo, pats, opts, revs):
1232 def _makegraphlogrevset(repo, pats, opts, revs):
1232 """Return (expr, filematcher) where expr is a revset string built
1233 """Return (expr, filematcher) where expr is a revset string built
1233 from log options and file patterns or None. If --stat or --patch
1234 from log options and file patterns or None. If --stat or --patch
1234 are not passed filematcher is None. Otherwise it is a callable
1235 are not passed filematcher is None. Otherwise it is a callable
1235 taking a revision number and returning a match objects filtering
1236 taking a revision number and returning a match objects filtering
1236 the files to be detailed when displaying the revision.
1237 the files to be detailed when displaying the revision.
1237 """
1238 """
1238 opt2revset = {
1239 opt2revset = {
1239 'no_merges': ('not merge()', None),
1240 'no_merges': ('not merge()', None),
1240 'only_merges': ('merge()', None),
1241 'only_merges': ('merge()', None),
1241 '_ancestors': ('ancestors(%(val)s)', None),
1242 '_ancestors': ('ancestors(%(val)s)', None),
1242 '_fancestors': ('_firstancestors(%(val)s)', None),
1243 '_fancestors': ('_firstancestors(%(val)s)', None),
1243 '_descendants': ('descendants(%(val)s)', None),
1244 '_descendants': ('descendants(%(val)s)', None),
1244 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1245 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1245 '_matchfiles': ('_matchfiles(%(val)s)', None),
1246 '_matchfiles': ('_matchfiles(%(val)s)', None),
1246 'date': ('date(%(val)r)', None),
1247 'date': ('date(%(val)r)', None),
1247 'branch': ('branch(%(val)r)', ' or '),
1248 'branch': ('branch(%(val)r)', ' or '),
1248 '_patslog': ('filelog(%(val)r)', ' or '),
1249 '_patslog': ('filelog(%(val)r)', ' or '),
1249 '_patsfollow': ('follow(%(val)r)', ' or '),
1250 '_patsfollow': ('follow(%(val)r)', ' or '),
1250 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1251 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1251 'keyword': ('keyword(%(val)r)', ' or '),
1252 'keyword': ('keyword(%(val)r)', ' or '),
1252 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1253 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1253 'user': ('user(%(val)r)', ' or '),
1254 'user': ('user(%(val)r)', ' or '),
1254 }
1255 }
1255
1256
1256 opts = dict(opts)
1257 opts = dict(opts)
1257 # follow or not follow?
1258 # follow or not follow?
1258 follow = opts.get('follow') or opts.get('follow_first')
1259 follow = opts.get('follow') or opts.get('follow_first')
1259 followfirst = opts.get('follow_first') and 1 or 0
1260 followfirst = opts.get('follow_first') and 1 or 0
1260 # --follow with FILE behaviour depends on revs...
1261 # --follow with FILE behaviour depends on revs...
1261 startrev = revs[0]
1262 startrev = revs[0]
1262 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1263 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1263
1264
1264 # branch and only_branch are really aliases and must be handled at
1265 # branch and only_branch are really aliases and must be handled at
1265 # the same time
1266 # the same time
1266 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1267 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1267 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1268 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1268 # pats/include/exclude are passed to match.match() directly in
1269 # pats/include/exclude are passed to match.match() directly in
1269 # _matchfiles() revset but walkchangerevs() builds its matcher with
1270 # _matchfiles() revset but walkchangerevs() builds its matcher with
1270 # scmutil.match(). The difference is input pats are globbed on
1271 # scmutil.match(). The difference is input pats are globbed on
1271 # platforms without shell expansion (windows).
1272 # platforms without shell expansion (windows).
1272 pctx = repo[None]
1273 pctx = repo[None]
1273 match, pats = scmutil.matchandpats(pctx, pats, opts)
1274 match, pats = scmutil.matchandpats(pctx, pats, opts)
1274 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1275 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1275 if not slowpath:
1276 if not slowpath:
1276 for f in match.files():
1277 for f in match.files():
1277 if follow and f not in pctx:
1278 if follow and f not in pctx:
1278 raise util.Abort(_('cannot follow file not in parent '
1279 raise util.Abort(_('cannot follow file not in parent '
1279 'revision: "%s"') % f)
1280 'revision: "%s"') % f)
1280 filelog = repo.file(f)
1281 filelog = repo.file(f)
1281 if not len(filelog):
1282 if not len(filelog):
1282 # A zero count may be a directory or deleted file, so
1283 # A zero count may be a directory or deleted file, so
1283 # try to find matching entries on the slow path.
1284 # try to find matching entries on the slow path.
1284 if follow:
1285 if follow:
1285 raise util.Abort(
1286 raise util.Abort(
1286 _('cannot follow nonexistent file: "%s"') % f)
1287 _('cannot follow nonexistent file: "%s"') % f)
1287 slowpath = True
1288 slowpath = True
1288 if slowpath:
1289 if slowpath:
1289 # See walkchangerevs() slow path.
1290 # See walkchangerevs() slow path.
1290 #
1291 #
1291 if follow:
1292 if follow:
1292 raise util.Abort(_('can only follow copies/renames for explicit '
1293 raise util.Abort(_('can only follow copies/renames for explicit '
1293 'filenames'))
1294 'filenames'))
1294 # pats/include/exclude cannot be represented as separate
1295 # pats/include/exclude cannot be represented as separate
1295 # revset expressions as their filtering logic applies at file
1296 # revset expressions as their filtering logic applies at file
1296 # level. For instance "-I a -X a" matches a revision touching
1297 # level. For instance "-I a -X a" matches a revision touching
1297 # "a" and "b" while "file(a) and not file(b)" does
1298 # "a" and "b" while "file(a) and not file(b)" does
1298 # not. Besides, filesets are evaluated against the working
1299 # not. Besides, filesets are evaluated against the working
1299 # directory.
1300 # directory.
1300 matchargs = ['r:', 'd:relpath']
1301 matchargs = ['r:', 'd:relpath']
1301 for p in pats:
1302 for p in pats:
1302 matchargs.append('p:' + p)
1303 matchargs.append('p:' + p)
1303 for p in opts.get('include', []):
1304 for p in opts.get('include', []):
1304 matchargs.append('i:' + p)
1305 matchargs.append('i:' + p)
1305 for p in opts.get('exclude', []):
1306 for p in opts.get('exclude', []):
1306 matchargs.append('x:' + p)
1307 matchargs.append('x:' + p)
1307 matchargs = ','.join(('%r' % p) for p in matchargs)
1308 matchargs = ','.join(('%r' % p) for p in matchargs)
1308 opts['_matchfiles'] = matchargs
1309 opts['_matchfiles'] = matchargs
1309 else:
1310 else:
1310 if follow:
1311 if follow:
1311 fpats = ('_patsfollow', '_patsfollowfirst')
1312 fpats = ('_patsfollow', '_patsfollowfirst')
1312 fnopats = (('_ancestors', '_fancestors'),
1313 fnopats = (('_ancestors', '_fancestors'),
1313 ('_descendants', '_fdescendants'))
1314 ('_descendants', '_fdescendants'))
1314 if pats:
1315 if pats:
1315 # follow() revset interprets its file argument as a
1316 # follow() revset interprets its file argument as a
1316 # manifest entry, so use match.files(), not pats.
1317 # manifest entry, so use match.files(), not pats.
1317 opts[fpats[followfirst]] = list(match.files())
1318 opts[fpats[followfirst]] = list(match.files())
1318 else:
1319 else:
1319 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1320 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1320 else:
1321 else:
1321 opts['_patslog'] = list(pats)
1322 opts['_patslog'] = list(pats)
1322
1323
1323 filematcher = None
1324 filematcher = None
1324 if opts.get('patch') or opts.get('stat'):
1325 if opts.get('patch') or opts.get('stat'):
1325 if follow:
1326 if follow:
1326 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1327 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1327 else:
1328 else:
1328 filematcher = lambda rev: match
1329 filematcher = lambda rev: match
1329
1330
1330 expr = []
1331 expr = []
1331 for op, val in opts.iteritems():
1332 for op, val in opts.iteritems():
1332 if not val:
1333 if not val:
1333 continue
1334 continue
1334 if op not in opt2revset:
1335 if op not in opt2revset:
1335 continue
1336 continue
1336 revop, andor = opt2revset[op]
1337 revop, andor = opt2revset[op]
1337 if '%(val)' not in revop:
1338 if '%(val)' not in revop:
1338 expr.append(revop)
1339 expr.append(revop)
1339 else:
1340 else:
1340 if not isinstance(val, list):
1341 if not isinstance(val, list):
1341 e = revop % {'val': val}
1342 e = revop % {'val': val}
1342 else:
1343 else:
1343 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1344 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1344 expr.append(e)
1345 expr.append(e)
1345
1346
1346 if expr:
1347 if expr:
1347 expr = '(' + ' and '.join(expr) + ')'
1348 expr = '(' + ' and '.join(expr) + ')'
1348 else:
1349 else:
1349 expr = None
1350 expr = None
1350 return expr, filematcher
1351 return expr, filematcher
1351
1352
1352 def getgraphlogrevs(repo, pats, opts):
1353 def getgraphlogrevs(repo, pats, opts):
1353 """Return (revs, expr, filematcher) where revs is an iterable of
1354 """Return (revs, expr, filematcher) where revs is an iterable of
1354 revision numbers, expr is a revset string built from log options
1355 revision numbers, expr is a revset string built from log options
1355 and file patterns or None, and used to filter 'revs'. If --stat or
1356 and file patterns or None, and used to filter 'revs'. If --stat or
1356 --patch are not passed filematcher is None. Otherwise it is a
1357 --patch are not passed filematcher is None. Otherwise it is a
1357 callable taking a revision number and returning a match objects
1358 callable taking a revision number and returning a match objects
1358 filtering the files to be detailed when displaying the revision.
1359 filtering the files to be detailed when displaying the revision.
1359 """
1360 """
1360 def increasingrevs(repo, revs, matcher):
1361 def increasingrevs(repo, revs, matcher):
1361 # The sorted input rev sequence is chopped in sub-sequences
1362 # The sorted input rev sequence is chopped in sub-sequences
1362 # which are sorted in ascending order and passed to the
1363 # which are sorted in ascending order and passed to the
1363 # matcher. The filtered revs are sorted again as they were in
1364 # matcher. The filtered revs are sorted again as they were in
1364 # the original sub-sequence. This achieve several things:
1365 # the original sub-sequence. This achieve several things:
1365 #
1366 #
1366 # - getlogrevs() now returns a generator which behaviour is
1367 # - getlogrevs() now returns a generator which behaviour is
1367 # adapted to log need. First results come fast, last ones
1368 # adapted to log need. First results come fast, last ones
1368 # are batched for performances.
1369 # are batched for performances.
1369 #
1370 #
1370 # - revset matchers often operate faster on revision in
1371 # - revset matchers often operate faster on revision in
1371 # changelog order, because most filters deal with the
1372 # changelog order, because most filters deal with the
1372 # changelog.
1373 # changelog.
1373 #
1374 #
1374 # - revset matchers can reorder revisions. "A or B" typically
1375 # - revset matchers can reorder revisions. "A or B" typically
1375 # returns returns the revision matching A then the revision
1376 # returns returns the revision matching A then the revision
1376 # matching B. We want to hide this internal implementation
1377 # matching B. We want to hide this internal implementation
1377 # detail from the caller, and sorting the filtered revision
1378 # detail from the caller, and sorting the filtered revision
1378 # again achieves this.
1379 # again achieves this.
1379 for i, window in increasingwindows(0, len(revs), windowsize=1):
1380 for i, window in increasingwindows(0, len(revs), windowsize=1):
1380 orevs = revs[i:i + window]
1381 orevs = revs[i:i + window]
1381 nrevs = set(matcher(repo, sorted(orevs)))
1382 nrevs = set(matcher(repo, sorted(orevs)))
1382 for rev in orevs:
1383 for rev in orevs:
1383 if rev in nrevs:
1384 if rev in nrevs:
1384 yield rev
1385 yield rev
1385
1386
1386 if not len(repo):
1387 if not len(repo):
1387 return iter([]), None, None
1388 return iter([]), None, None
1388 # Default --rev value depends on --follow but --follow behaviour
1389 # Default --rev value depends on --follow but --follow behaviour
1389 # depends on revisions resolved from --rev...
1390 # depends on revisions resolved from --rev...
1390 follow = opts.get('follow') or opts.get('follow_first')
1391 follow = opts.get('follow') or opts.get('follow_first')
1391 if opts.get('rev'):
1392 if opts.get('rev'):
1392 revs = scmutil.revrange(repo, opts['rev'])
1393 revs = scmutil.revrange(repo, opts['rev'])
1393 else:
1394 else:
1394 if follow and len(repo) > 0:
1395 if follow and len(repo) > 0:
1395 revs = scmutil.revrange(repo, ['.:0'])
1396 revs = repo.revs('reverse(:.)')
1396 else:
1397 else:
1397 revs = list(repo.changelog)
1398 revs = list(repo.changelog)
1398 revs.reverse()
1399 revs.reverse()
1399 if not revs:
1400 if not revs:
1400 return iter([]), None, None
1401 return iter([]), None, None
1401 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1402 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1402 if expr:
1403 if expr:
1403 matcher = revset.match(repo.ui, expr)
1404 matcher = revset.match(repo.ui, expr)
1404 revs = increasingrevs(repo, revs, matcher)
1405 revs = increasingrevs(repo, revs, matcher)
1405 if not opts.get('hidden'):
1406 if not opts.get('hidden'):
1406 # --hidden is still experimental and not worth a dedicated revset
1407 # --hidden is still experimental and not worth a dedicated revset
1407 # yet. Fortunately, filtering revision number is fast.
1408 # yet. Fortunately, filtering revision number is fast.
1408 revs = (r for r in revs if r not in repo.hiddenrevs)
1409 revs = (r for r in revs if r not in repo.hiddenrevs)
1409 else:
1410 else:
1410 revs = iter(revs)
1411 revs = iter(revs)
1411 return revs, expr, filematcher
1412 return revs, expr, filematcher
1412
1413
1413 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1414 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1414 filematcher=None):
1415 filematcher=None):
1415 seen, state = [], graphmod.asciistate()
1416 seen, state = [], graphmod.asciistate()
1416 for rev, type, ctx, parents in dag:
1417 for rev, type, ctx, parents in dag:
1417 char = 'o'
1418 char = 'o'
1418 if ctx.node() in showparents:
1419 if ctx.node() in showparents:
1419 char = '@'
1420 char = '@'
1420 elif ctx.obsolete():
1421 elif ctx.obsolete():
1421 char = 'x'
1422 char = 'x'
1422 copies = None
1423 copies = None
1423 if getrenamed and ctx.rev():
1424 if getrenamed and ctx.rev():
1424 copies = []
1425 copies = []
1425 for fn in ctx.files():
1426 for fn in ctx.files():
1426 rename = getrenamed(fn, ctx.rev())
1427 rename = getrenamed(fn, ctx.rev())
1427 if rename:
1428 if rename:
1428 copies.append((fn, rename[0]))
1429 copies.append((fn, rename[0]))
1429 revmatchfn = None
1430 revmatchfn = None
1430 if filematcher is not None:
1431 if filematcher is not None:
1431 revmatchfn = filematcher(ctx.rev())
1432 revmatchfn = filematcher(ctx.rev())
1432 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1433 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1433 lines = displayer.hunk.pop(rev).split('\n')
1434 lines = displayer.hunk.pop(rev).split('\n')
1434 if not lines[-1]:
1435 if not lines[-1]:
1435 del lines[-1]
1436 del lines[-1]
1436 displayer.flush(rev)
1437 displayer.flush(rev)
1437 edges = edgefn(type, char, lines, seen, rev, parents)
1438 edges = edgefn(type, char, lines, seen, rev, parents)
1438 for type, char, lines, coldata in edges:
1439 for type, char, lines, coldata in edges:
1439 graphmod.ascii(ui, state, type, char, lines, coldata)
1440 graphmod.ascii(ui, state, type, char, lines, coldata)
1440 displayer.close()
1441 displayer.close()
1441
1442
1442 def graphlog(ui, repo, *pats, **opts):
1443 def graphlog(ui, repo, *pats, **opts):
1443 # Parameters are identical to log command ones
1444 # Parameters are identical to log command ones
1444 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1445 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1445 revs = sorted(revs, reverse=1)
1446 revs = sorted(revs, reverse=1)
1446 limit = loglimit(opts)
1447 limit = loglimit(opts)
1447 if limit is not None:
1448 if limit is not None:
1448 revs = revs[:limit]
1449 revs = revs[:limit]
1449 revdag = graphmod.dagwalker(repo, revs)
1450 revdag = graphmod.dagwalker(repo, revs)
1450
1451
1451 getrenamed = None
1452 getrenamed = None
1452 if opts.get('copies'):
1453 if opts.get('copies'):
1453 endrev = None
1454 endrev = None
1454 if opts.get('rev'):
1455 if opts.get('rev'):
1455 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1456 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1456 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1457 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1457 displayer = show_changeset(ui, repo, opts, buffered=True)
1458 displayer = show_changeset(ui, repo, opts, buffered=True)
1458 showparents = [ctx.node() for ctx in repo[None].parents()]
1459 showparents = [ctx.node() for ctx in repo[None].parents()]
1459 displaygraph(ui, revdag, displayer, showparents,
1460 displaygraph(ui, revdag, displayer, showparents,
1460 graphmod.asciiedges, getrenamed, filematcher)
1461 graphmod.asciiedges, getrenamed, filematcher)
1461
1462
1462 def checkunsupportedgraphflags(pats, opts):
1463 def checkunsupportedgraphflags(pats, opts):
1463 for op in ["newest_first"]:
1464 for op in ["newest_first"]:
1464 if op in opts and opts[op]:
1465 if op in opts and opts[op]:
1465 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1466 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1466 % op.replace("_", "-"))
1467 % op.replace("_", "-"))
1467
1468
1468 def graphrevs(repo, nodes, opts):
1469 def graphrevs(repo, nodes, opts):
1469 limit = loglimit(opts)
1470 limit = loglimit(opts)
1470 nodes.reverse()
1471 nodes.reverse()
1471 if limit is not None:
1472 if limit is not None:
1472 nodes = nodes[:limit]
1473 nodes = nodes[:limit]
1473 return graphmod.nodes(repo, nodes)
1474 return graphmod.nodes(repo, nodes)
1474
1475
1475 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1476 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1476 join = lambda f: os.path.join(prefix, f)
1477 join = lambda f: os.path.join(prefix, f)
1477 bad = []
1478 bad = []
1478 oldbad = match.bad
1479 oldbad = match.bad
1479 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1480 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1480 names = []
1481 names = []
1481 wctx = repo[None]
1482 wctx = repo[None]
1482 cca = None
1483 cca = None
1483 abort, warn = scmutil.checkportabilityalert(ui)
1484 abort, warn = scmutil.checkportabilityalert(ui)
1484 if abort or warn:
1485 if abort or warn:
1485 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1486 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1486 for f in repo.walk(match):
1487 for f in repo.walk(match):
1487 exact = match.exact(f)
1488 exact = match.exact(f)
1488 if exact or not explicitonly and f not in repo.dirstate:
1489 if exact or not explicitonly and f not in repo.dirstate:
1489 if cca:
1490 if cca:
1490 cca(f)
1491 cca(f)
1491 names.append(f)
1492 names.append(f)
1492 if ui.verbose or not exact:
1493 if ui.verbose or not exact:
1493 ui.status(_('adding %s\n') % match.rel(join(f)))
1494 ui.status(_('adding %s\n') % match.rel(join(f)))
1494
1495
1495 for subpath in wctx.substate:
1496 for subpath in wctx.substate:
1496 sub = wctx.sub(subpath)
1497 sub = wctx.sub(subpath)
1497 try:
1498 try:
1498 submatch = matchmod.narrowmatcher(subpath, match)
1499 submatch = matchmod.narrowmatcher(subpath, match)
1499 if listsubrepos:
1500 if listsubrepos:
1500 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1501 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1501 False))
1502 False))
1502 else:
1503 else:
1503 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1504 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1504 True))
1505 True))
1505 except error.LookupError:
1506 except error.LookupError:
1506 ui.status(_("skipping missing subrepository: %s\n")
1507 ui.status(_("skipping missing subrepository: %s\n")
1507 % join(subpath))
1508 % join(subpath))
1508
1509
1509 if not dryrun:
1510 if not dryrun:
1510 rejected = wctx.add(names, prefix)
1511 rejected = wctx.add(names, prefix)
1511 bad.extend(f for f in rejected if f in match.files())
1512 bad.extend(f for f in rejected if f in match.files())
1512 return bad
1513 return bad
1513
1514
1514 def forget(ui, repo, match, prefix, explicitonly):
1515 def forget(ui, repo, match, prefix, explicitonly):
1515 join = lambda f: os.path.join(prefix, f)
1516 join = lambda f: os.path.join(prefix, f)
1516 bad = []
1517 bad = []
1517 oldbad = match.bad
1518 oldbad = match.bad
1518 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1519 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1519 wctx = repo[None]
1520 wctx = repo[None]
1520 forgot = []
1521 forgot = []
1521 s = repo.status(match=match, clean=True)
1522 s = repo.status(match=match, clean=True)
1522 forget = sorted(s[0] + s[1] + s[3] + s[6])
1523 forget = sorted(s[0] + s[1] + s[3] + s[6])
1523 if explicitonly:
1524 if explicitonly:
1524 forget = [f for f in forget if match.exact(f)]
1525 forget = [f for f in forget if match.exact(f)]
1525
1526
1526 for subpath in wctx.substate:
1527 for subpath in wctx.substate:
1527 sub = wctx.sub(subpath)
1528 sub = wctx.sub(subpath)
1528 try:
1529 try:
1529 submatch = matchmod.narrowmatcher(subpath, match)
1530 submatch = matchmod.narrowmatcher(subpath, match)
1530 subbad, subforgot = sub.forget(ui, submatch, prefix)
1531 subbad, subforgot = sub.forget(ui, submatch, prefix)
1531 bad.extend([subpath + '/' + f for f in subbad])
1532 bad.extend([subpath + '/' + f for f in subbad])
1532 forgot.extend([subpath + '/' + f for f in subforgot])
1533 forgot.extend([subpath + '/' + f for f in subforgot])
1533 except error.LookupError:
1534 except error.LookupError:
1534 ui.status(_("skipping missing subrepository: %s\n")
1535 ui.status(_("skipping missing subrepository: %s\n")
1535 % join(subpath))
1536 % join(subpath))
1536
1537
1537 if not explicitonly:
1538 if not explicitonly:
1538 for f in match.files():
1539 for f in match.files():
1539 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1540 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1540 if f not in forgot:
1541 if f not in forgot:
1541 if os.path.exists(match.rel(join(f))):
1542 if os.path.exists(match.rel(join(f))):
1542 ui.warn(_('not removing %s: '
1543 ui.warn(_('not removing %s: '
1543 'file is already untracked\n')
1544 'file is already untracked\n')
1544 % match.rel(join(f)))
1545 % match.rel(join(f)))
1545 bad.append(f)
1546 bad.append(f)
1546
1547
1547 for f in forget:
1548 for f in forget:
1548 if ui.verbose or not match.exact(f):
1549 if ui.verbose or not match.exact(f):
1549 ui.status(_('removing %s\n') % match.rel(join(f)))
1550 ui.status(_('removing %s\n') % match.rel(join(f)))
1550
1551
1551 rejected = wctx.forget(forget, prefix)
1552 rejected = wctx.forget(forget, prefix)
1552 bad.extend(f for f in rejected if f in match.files())
1553 bad.extend(f for f in rejected if f in match.files())
1553 forgot.extend(forget)
1554 forgot.extend(forget)
1554 return bad, forgot
1555 return bad, forgot
1555
1556
1556 def duplicatecopies(repo, rev, p1):
1557 def duplicatecopies(repo, rev, p1):
1557 "Reproduce copies found in the source revision in the dirstate for grafts"
1558 "Reproduce copies found in the source revision in the dirstate for grafts"
1558 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1559 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1559 repo.dirstate.copy(src, dst)
1560 repo.dirstate.copy(src, dst)
1560
1561
1561 def commit(ui, repo, commitfunc, pats, opts):
1562 def commit(ui, repo, commitfunc, pats, opts):
1562 '''commit the specified files or all outstanding changes'''
1563 '''commit the specified files or all outstanding changes'''
1563 date = opts.get('date')
1564 date = opts.get('date')
1564 if date:
1565 if date:
1565 opts['date'] = util.parsedate(date)
1566 opts['date'] = util.parsedate(date)
1566 message = logmessage(ui, opts)
1567 message = logmessage(ui, opts)
1567
1568
1568 # extract addremove carefully -- this function can be called from a command
1569 # extract addremove carefully -- this function can be called from a command
1569 # that doesn't support addremove
1570 # that doesn't support addremove
1570 if opts.get('addremove'):
1571 if opts.get('addremove'):
1571 scmutil.addremove(repo, pats, opts)
1572 scmutil.addremove(repo, pats, opts)
1572
1573
1573 return commitfunc(ui, repo, message,
1574 return commitfunc(ui, repo, message,
1574 scmutil.match(repo[None], pats, opts), opts)
1575 scmutil.match(repo[None], pats, opts), opts)
1575
1576
1576 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1577 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1577 ui.note(_('amending changeset %s\n') % old)
1578 ui.note(_('amending changeset %s\n') % old)
1578 base = old.p1()
1579 base = old.p1()
1579
1580
1580 wlock = lock = None
1581 wlock = lock = None
1581 try:
1582 try:
1582 wlock = repo.wlock()
1583 wlock = repo.wlock()
1583 lock = repo.lock()
1584 lock = repo.lock()
1584 tr = repo.transaction('amend')
1585 tr = repo.transaction('amend')
1585 try:
1586 try:
1586 # See if we got a message from -m or -l, if not, open the editor
1587 # See if we got a message from -m or -l, if not, open the editor
1587 # with the message of the changeset to amend
1588 # with the message of the changeset to amend
1588 message = logmessage(ui, opts)
1589 message = logmessage(ui, opts)
1589 # First, do a regular commit to record all changes in the working
1590 # First, do a regular commit to record all changes in the working
1590 # directory (if there are any)
1591 # directory (if there are any)
1591 ui.callhooks = False
1592 ui.callhooks = False
1592 try:
1593 try:
1593 opts['message'] = 'temporary amend commit for %s' % old
1594 opts['message'] = 'temporary amend commit for %s' % old
1594 node = commit(ui, repo, commitfunc, pats, opts)
1595 node = commit(ui, repo, commitfunc, pats, opts)
1595 finally:
1596 finally:
1596 ui.callhooks = True
1597 ui.callhooks = True
1597 ctx = repo[node]
1598 ctx = repo[node]
1598
1599
1599 # Participating changesets:
1600 # Participating changesets:
1600 #
1601 #
1601 # node/ctx o - new (intermediate) commit that contains changes
1602 # node/ctx o - new (intermediate) commit that contains changes
1602 # | from working dir to go into amending commit
1603 # | from working dir to go into amending commit
1603 # | (or a workingctx if there were no changes)
1604 # | (or a workingctx if there were no changes)
1604 # |
1605 # |
1605 # old o - changeset to amend
1606 # old o - changeset to amend
1606 # |
1607 # |
1607 # base o - parent of amending changeset
1608 # base o - parent of amending changeset
1608
1609
1609 # Update extra dict from amended commit (e.g. to preserve graft
1610 # Update extra dict from amended commit (e.g. to preserve graft
1610 # source)
1611 # source)
1611 extra.update(old.extra())
1612 extra.update(old.extra())
1612
1613
1613 # Also update it from the intermediate commit or from the wctx
1614 # Also update it from the intermediate commit or from the wctx
1614 extra.update(ctx.extra())
1615 extra.update(ctx.extra())
1615
1616
1616 files = set(old.files())
1617 files = set(old.files())
1617
1618
1618 # Second, we use either the commit we just did, or if there were no
1619 # Second, we use either the commit we just did, or if there were no
1619 # changes the parent of the working directory as the version of the
1620 # changes the parent of the working directory as the version of the
1620 # files in the final amend commit
1621 # files in the final amend commit
1621 if node:
1622 if node:
1622 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1623 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1623
1624
1624 user = ctx.user()
1625 user = ctx.user()
1625 date = ctx.date()
1626 date = ctx.date()
1626 # Recompute copies (avoid recording a -> b -> a)
1627 # Recompute copies (avoid recording a -> b -> a)
1627 copied = copies.pathcopies(base, ctx)
1628 copied = copies.pathcopies(base, ctx)
1628
1629
1629 # Prune files which were reverted by the updates: if old
1630 # Prune files which were reverted by the updates: if old
1630 # introduced file X and our intermediate commit, node,
1631 # introduced file X and our intermediate commit, node,
1631 # renamed that file, then those two files are the same and
1632 # renamed that file, then those two files are the same and
1632 # we can discard X from our list of files. Likewise if X
1633 # we can discard X from our list of files. Likewise if X
1633 # was deleted, it's no longer relevant
1634 # was deleted, it's no longer relevant
1634 files.update(ctx.files())
1635 files.update(ctx.files())
1635
1636
1636 def samefile(f):
1637 def samefile(f):
1637 if f in ctx.manifest():
1638 if f in ctx.manifest():
1638 a = ctx.filectx(f)
1639 a = ctx.filectx(f)
1639 if f in base.manifest():
1640 if f in base.manifest():
1640 b = base.filectx(f)
1641 b = base.filectx(f)
1641 return (not a.cmp(b)
1642 return (not a.cmp(b)
1642 and a.flags() == b.flags())
1643 and a.flags() == b.flags())
1643 else:
1644 else:
1644 return False
1645 return False
1645 else:
1646 else:
1646 return f not in base.manifest()
1647 return f not in base.manifest()
1647 files = [f for f in files if not samefile(f)]
1648 files = [f for f in files if not samefile(f)]
1648
1649
1649 def filectxfn(repo, ctx_, path):
1650 def filectxfn(repo, ctx_, path):
1650 try:
1651 try:
1651 fctx = ctx[path]
1652 fctx = ctx[path]
1652 flags = fctx.flags()
1653 flags = fctx.flags()
1653 mctx = context.memfilectx(fctx.path(), fctx.data(),
1654 mctx = context.memfilectx(fctx.path(), fctx.data(),
1654 islink='l' in flags,
1655 islink='l' in flags,
1655 isexec='x' in flags,
1656 isexec='x' in flags,
1656 copied=copied.get(path))
1657 copied=copied.get(path))
1657 return mctx
1658 return mctx
1658 except KeyError:
1659 except KeyError:
1659 raise IOError
1660 raise IOError
1660 else:
1661 else:
1661 ui.note(_('copying changeset %s to %s\n') % (old, base))
1662 ui.note(_('copying changeset %s to %s\n') % (old, base))
1662
1663
1663 # Use version of files as in the old cset
1664 # Use version of files as in the old cset
1664 def filectxfn(repo, ctx_, path):
1665 def filectxfn(repo, ctx_, path):
1665 try:
1666 try:
1666 return old.filectx(path)
1667 return old.filectx(path)
1667 except KeyError:
1668 except KeyError:
1668 raise IOError
1669 raise IOError
1669
1670
1670 user = opts.get('user') or old.user()
1671 user = opts.get('user') or old.user()
1671 date = opts.get('date') or old.date()
1672 date = opts.get('date') or old.date()
1672 if not message:
1673 if not message:
1673 message = old.description()
1674 message = old.description()
1674
1675
1675 new = context.memctx(repo,
1676 new = context.memctx(repo,
1676 parents=[base.node(), nullid],
1677 parents=[base.node(), nullid],
1677 text=message,
1678 text=message,
1678 files=files,
1679 files=files,
1679 filectxfn=filectxfn,
1680 filectxfn=filectxfn,
1680 user=user,
1681 user=user,
1681 date=date,
1682 date=date,
1682 extra=extra)
1683 extra=extra)
1683 new._text = commitforceeditor(repo, new, [])
1684 new._text = commitforceeditor(repo, new, [])
1684 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1685 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1685 try:
1686 try:
1686 repo.ui.setconfig('phases', 'new-commit', old.phase())
1687 repo.ui.setconfig('phases', 'new-commit', old.phase())
1687 newid = repo.commitctx(new)
1688 newid = repo.commitctx(new)
1688 finally:
1689 finally:
1689 repo.ui.setconfig('phases', 'new-commit', ph)
1690 repo.ui.setconfig('phases', 'new-commit', ph)
1690 if newid != old.node():
1691 if newid != old.node():
1691 # Reroute the working copy parent to the new changeset
1692 # Reroute the working copy parent to the new changeset
1692 repo.setparents(newid, nullid)
1693 repo.setparents(newid, nullid)
1693
1694
1694 # Move bookmarks from old parent to amend commit
1695 # Move bookmarks from old parent to amend commit
1695 bms = repo.nodebookmarks(old.node())
1696 bms = repo.nodebookmarks(old.node())
1696 if bms:
1697 if bms:
1697 for bm in bms:
1698 for bm in bms:
1698 repo._bookmarks[bm] = newid
1699 repo._bookmarks[bm] = newid
1699 bookmarks.write(repo)
1700 bookmarks.write(repo)
1700 #commit the whole amend process
1701 #commit the whole amend process
1701 if obsolete._enabled and newid != old.node():
1702 if obsolete._enabled and newid != old.node():
1702 # mark the new changeset as successor of the rewritten one
1703 # mark the new changeset as successor of the rewritten one
1703 new = repo[newid]
1704 new = repo[newid]
1704 obs = [(old, (new,))]
1705 obs = [(old, (new,))]
1705 if node:
1706 if node:
1706 obs.append((ctx, (new,)))
1707 obs.append((ctx, (new,)))
1707
1708
1708 obsolete.createmarkers(repo, obs)
1709 obsolete.createmarkers(repo, obs)
1709 tr.close()
1710 tr.close()
1710 finally:
1711 finally:
1711 tr.release()
1712 tr.release()
1712 if (not obsolete._enabled) and newid != old.node():
1713 if (not obsolete._enabled) and newid != old.node():
1713 # Strip the intermediate commit (if there was one) and the amended
1714 # Strip the intermediate commit (if there was one) and the amended
1714 # commit
1715 # commit
1715 if node:
1716 if node:
1716 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1717 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1717 ui.note(_('stripping amended changeset %s\n') % old)
1718 ui.note(_('stripping amended changeset %s\n') % old)
1718 repair.strip(ui, repo, old.node(), topic='amend-backup')
1719 repair.strip(ui, repo, old.node(), topic='amend-backup')
1719 finally:
1720 finally:
1720 lockmod.release(wlock, lock)
1721 lockmod.release(wlock, lock)
1721 return newid
1722 return newid
1722
1723
1723 def commiteditor(repo, ctx, subs):
1724 def commiteditor(repo, ctx, subs):
1724 if ctx.description():
1725 if ctx.description():
1725 return ctx.description()
1726 return ctx.description()
1726 return commitforceeditor(repo, ctx, subs)
1727 return commitforceeditor(repo, ctx, subs)
1727
1728
1728 def commitforceeditor(repo, ctx, subs):
1729 def commitforceeditor(repo, ctx, subs):
1729 edittext = []
1730 edittext = []
1730 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1731 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1731 if ctx.description():
1732 if ctx.description():
1732 edittext.append(ctx.description())
1733 edittext.append(ctx.description())
1733 edittext.append("")
1734 edittext.append("")
1734 edittext.append("") # Empty line between message and comments.
1735 edittext.append("") # Empty line between message and comments.
1735 edittext.append(_("HG: Enter commit message."
1736 edittext.append(_("HG: Enter commit message."
1736 " Lines beginning with 'HG:' are removed."))
1737 " Lines beginning with 'HG:' are removed."))
1737 edittext.append(_("HG: Leave message empty to abort commit."))
1738 edittext.append(_("HG: Leave message empty to abort commit."))
1738 edittext.append("HG: --")
1739 edittext.append("HG: --")
1739 edittext.append(_("HG: user: %s") % ctx.user())
1740 edittext.append(_("HG: user: %s") % ctx.user())
1740 if ctx.p2():
1741 if ctx.p2():
1741 edittext.append(_("HG: branch merge"))
1742 edittext.append(_("HG: branch merge"))
1742 if ctx.branch():
1743 if ctx.branch():
1743 edittext.append(_("HG: branch '%s'") % ctx.branch())
1744 edittext.append(_("HG: branch '%s'") % ctx.branch())
1744 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1745 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1745 edittext.extend([_("HG: added %s") % f for f in added])
1746 edittext.extend([_("HG: added %s") % f for f in added])
1746 edittext.extend([_("HG: changed %s") % f for f in modified])
1747 edittext.extend([_("HG: changed %s") % f for f in modified])
1747 edittext.extend([_("HG: removed %s") % f for f in removed])
1748 edittext.extend([_("HG: removed %s") % f for f in removed])
1748 if not added and not modified and not removed:
1749 if not added and not modified and not removed:
1749 edittext.append(_("HG: no files changed"))
1750 edittext.append(_("HG: no files changed"))
1750 edittext.append("")
1751 edittext.append("")
1751 # run editor in the repository root
1752 # run editor in the repository root
1752 olddir = os.getcwd()
1753 olddir = os.getcwd()
1753 os.chdir(repo.root)
1754 os.chdir(repo.root)
1754 text = repo.ui.edit("\n".join(edittext), ctx.user())
1755 text = repo.ui.edit("\n".join(edittext), ctx.user())
1755 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1756 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1756 os.chdir(olddir)
1757 os.chdir(olddir)
1757
1758
1758 if not text.strip():
1759 if not text.strip():
1759 raise util.Abort(_("empty commit message"))
1760 raise util.Abort(_("empty commit message"))
1760
1761
1761 return text
1762 return text
1762
1763
1763 def revert(ui, repo, ctx, parents, *pats, **opts):
1764 def revert(ui, repo, ctx, parents, *pats, **opts):
1764 parent, p2 = parents
1765 parent, p2 = parents
1765 node = ctx.node()
1766 node = ctx.node()
1766
1767
1767 mf = ctx.manifest()
1768 mf = ctx.manifest()
1768 if node == parent:
1769 if node == parent:
1769 pmf = mf
1770 pmf = mf
1770 else:
1771 else:
1771 pmf = None
1772 pmf = None
1772
1773
1773 # need all matching names in dirstate and manifest of target rev,
1774 # need all matching names in dirstate and manifest of target rev,
1774 # so have to walk both. do not print errors if files exist in one
1775 # so have to walk both. do not print errors if files exist in one
1775 # but not other.
1776 # but not other.
1776
1777
1777 names = {}
1778 names = {}
1778
1779
1779 wlock = repo.wlock()
1780 wlock = repo.wlock()
1780 try:
1781 try:
1781 # walk dirstate.
1782 # walk dirstate.
1782
1783
1783 m = scmutil.match(repo[None], pats, opts)
1784 m = scmutil.match(repo[None], pats, opts)
1784 m.bad = lambda x, y: False
1785 m.bad = lambda x, y: False
1785 for abs in repo.walk(m):
1786 for abs in repo.walk(m):
1786 names[abs] = m.rel(abs), m.exact(abs)
1787 names[abs] = m.rel(abs), m.exact(abs)
1787
1788
1788 # walk target manifest.
1789 # walk target manifest.
1789
1790
1790 def badfn(path, msg):
1791 def badfn(path, msg):
1791 if path in names:
1792 if path in names:
1792 return
1793 return
1793 if path in ctx.substate:
1794 if path in ctx.substate:
1794 return
1795 return
1795 path_ = path + '/'
1796 path_ = path + '/'
1796 for f in names:
1797 for f in names:
1797 if f.startswith(path_):
1798 if f.startswith(path_):
1798 return
1799 return
1799 ui.warn("%s: %s\n" % (m.rel(path), msg))
1800 ui.warn("%s: %s\n" % (m.rel(path), msg))
1800
1801
1801 m = scmutil.match(ctx, pats, opts)
1802 m = scmutil.match(ctx, pats, opts)
1802 m.bad = badfn
1803 m.bad = badfn
1803 for abs in ctx.walk(m):
1804 for abs in ctx.walk(m):
1804 if abs not in names:
1805 if abs not in names:
1805 names[abs] = m.rel(abs), m.exact(abs)
1806 names[abs] = m.rel(abs), m.exact(abs)
1806
1807
1807 # get the list of subrepos that must be reverted
1808 # get the list of subrepos that must be reverted
1808 targetsubs = [s for s in ctx.substate if m(s)]
1809 targetsubs = [s for s in ctx.substate if m(s)]
1809 m = scmutil.matchfiles(repo, names)
1810 m = scmutil.matchfiles(repo, names)
1810 changes = repo.status(match=m)[:4]
1811 changes = repo.status(match=m)[:4]
1811 modified, added, removed, deleted = map(set, changes)
1812 modified, added, removed, deleted = map(set, changes)
1812
1813
1813 # if f is a rename, also revert the source
1814 # if f is a rename, also revert the source
1814 cwd = repo.getcwd()
1815 cwd = repo.getcwd()
1815 for f in added:
1816 for f in added:
1816 src = repo.dirstate.copied(f)
1817 src = repo.dirstate.copied(f)
1817 if src and src not in names and repo.dirstate[src] == 'r':
1818 if src and src not in names and repo.dirstate[src] == 'r':
1818 removed.add(src)
1819 removed.add(src)
1819 names[src] = (repo.pathto(src, cwd), True)
1820 names[src] = (repo.pathto(src, cwd), True)
1820
1821
1821 def removeforget(abs):
1822 def removeforget(abs):
1822 if repo.dirstate[abs] == 'a':
1823 if repo.dirstate[abs] == 'a':
1823 return _('forgetting %s\n')
1824 return _('forgetting %s\n')
1824 return _('removing %s\n')
1825 return _('removing %s\n')
1825
1826
1826 revert = ([], _('reverting %s\n'))
1827 revert = ([], _('reverting %s\n'))
1827 add = ([], _('adding %s\n'))
1828 add = ([], _('adding %s\n'))
1828 remove = ([], removeforget)
1829 remove = ([], removeforget)
1829 undelete = ([], _('undeleting %s\n'))
1830 undelete = ([], _('undeleting %s\n'))
1830
1831
1831 disptable = (
1832 disptable = (
1832 # dispatch table:
1833 # dispatch table:
1833 # file state
1834 # file state
1834 # action if in target manifest
1835 # action if in target manifest
1835 # action if not in target manifest
1836 # action if not in target manifest
1836 # make backup if in target manifest
1837 # make backup if in target manifest
1837 # make backup if not in target manifest
1838 # make backup if not in target manifest
1838 (modified, revert, remove, True, True),
1839 (modified, revert, remove, True, True),
1839 (added, revert, remove, True, False),
1840 (added, revert, remove, True, False),
1840 (removed, undelete, None, False, False),
1841 (removed, undelete, None, False, False),
1841 (deleted, revert, remove, False, False),
1842 (deleted, revert, remove, False, False),
1842 )
1843 )
1843
1844
1844 for abs, (rel, exact) in sorted(names.items()):
1845 for abs, (rel, exact) in sorted(names.items()):
1845 mfentry = mf.get(abs)
1846 mfentry = mf.get(abs)
1846 target = repo.wjoin(abs)
1847 target = repo.wjoin(abs)
1847 def handle(xlist, dobackup):
1848 def handle(xlist, dobackup):
1848 xlist[0].append(abs)
1849 xlist[0].append(abs)
1849 if (dobackup and not opts.get('no_backup') and
1850 if (dobackup and not opts.get('no_backup') and
1850 os.path.lexists(target)):
1851 os.path.lexists(target)):
1851 bakname = "%s.orig" % rel
1852 bakname = "%s.orig" % rel
1852 ui.note(_('saving current version of %s as %s\n') %
1853 ui.note(_('saving current version of %s as %s\n') %
1853 (rel, bakname))
1854 (rel, bakname))
1854 if not opts.get('dry_run'):
1855 if not opts.get('dry_run'):
1855 util.rename(target, bakname)
1856 util.rename(target, bakname)
1856 if ui.verbose or not exact:
1857 if ui.verbose or not exact:
1857 msg = xlist[1]
1858 msg = xlist[1]
1858 if not isinstance(msg, basestring):
1859 if not isinstance(msg, basestring):
1859 msg = msg(abs)
1860 msg = msg(abs)
1860 ui.status(msg % rel)
1861 ui.status(msg % rel)
1861 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1862 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1862 if abs not in table:
1863 if abs not in table:
1863 continue
1864 continue
1864 # file has changed in dirstate
1865 # file has changed in dirstate
1865 if mfentry:
1866 if mfentry:
1866 handle(hitlist, backuphit)
1867 handle(hitlist, backuphit)
1867 elif misslist is not None:
1868 elif misslist is not None:
1868 handle(misslist, backupmiss)
1869 handle(misslist, backupmiss)
1869 break
1870 break
1870 else:
1871 else:
1871 if abs not in repo.dirstate:
1872 if abs not in repo.dirstate:
1872 if mfentry:
1873 if mfentry:
1873 handle(add, True)
1874 handle(add, True)
1874 elif exact:
1875 elif exact:
1875 ui.warn(_('file not managed: %s\n') % rel)
1876 ui.warn(_('file not managed: %s\n') % rel)
1876 continue
1877 continue
1877 # file has not changed in dirstate
1878 # file has not changed in dirstate
1878 if node == parent:
1879 if node == parent:
1879 if exact:
1880 if exact:
1880 ui.warn(_('no changes needed to %s\n') % rel)
1881 ui.warn(_('no changes needed to %s\n') % rel)
1881 continue
1882 continue
1882 if pmf is None:
1883 if pmf is None:
1883 # only need parent manifest in this unlikely case,
1884 # only need parent manifest in this unlikely case,
1884 # so do not read by default
1885 # so do not read by default
1885 pmf = repo[parent].manifest()
1886 pmf = repo[parent].manifest()
1886 if abs in pmf and mfentry:
1887 if abs in pmf and mfentry:
1887 # if version of file is same in parent and target
1888 # if version of file is same in parent and target
1888 # manifests, do nothing
1889 # manifests, do nothing
1889 if (pmf[abs] != mfentry or
1890 if (pmf[abs] != mfentry or
1890 pmf.flags(abs) != mf.flags(abs)):
1891 pmf.flags(abs) != mf.flags(abs)):
1891 handle(revert, False)
1892 handle(revert, False)
1892 else:
1893 else:
1893 handle(remove, False)
1894 handle(remove, False)
1894
1895
1895 if not opts.get('dry_run'):
1896 if not opts.get('dry_run'):
1896 def checkout(f):
1897 def checkout(f):
1897 fc = ctx[f]
1898 fc = ctx[f]
1898 repo.wwrite(f, fc.data(), fc.flags())
1899 repo.wwrite(f, fc.data(), fc.flags())
1899
1900
1900 audit_path = scmutil.pathauditor(repo.root)
1901 audit_path = scmutil.pathauditor(repo.root)
1901 for f in remove[0]:
1902 for f in remove[0]:
1902 if repo.dirstate[f] == 'a':
1903 if repo.dirstate[f] == 'a':
1903 repo.dirstate.drop(f)
1904 repo.dirstate.drop(f)
1904 continue
1905 continue
1905 audit_path(f)
1906 audit_path(f)
1906 try:
1907 try:
1907 util.unlinkpath(repo.wjoin(f))
1908 util.unlinkpath(repo.wjoin(f))
1908 except OSError:
1909 except OSError:
1909 pass
1910 pass
1910 repo.dirstate.remove(f)
1911 repo.dirstate.remove(f)
1911
1912
1912 normal = None
1913 normal = None
1913 if node == parent:
1914 if node == parent:
1914 # We're reverting to our parent. If possible, we'd like status
1915 # We're reverting to our parent. If possible, we'd like status
1915 # to report the file as clean. We have to use normallookup for
1916 # to report the file as clean. We have to use normallookup for
1916 # merges to avoid losing information about merged/dirty files.
1917 # merges to avoid losing information about merged/dirty files.
1917 if p2 != nullid:
1918 if p2 != nullid:
1918 normal = repo.dirstate.normallookup
1919 normal = repo.dirstate.normallookup
1919 else:
1920 else:
1920 normal = repo.dirstate.normal
1921 normal = repo.dirstate.normal
1921 for f in revert[0]:
1922 for f in revert[0]:
1922 checkout(f)
1923 checkout(f)
1923 if normal:
1924 if normal:
1924 normal(f)
1925 normal(f)
1925
1926
1926 for f in add[0]:
1927 for f in add[0]:
1927 checkout(f)
1928 checkout(f)
1928 repo.dirstate.add(f)
1929 repo.dirstate.add(f)
1929
1930
1930 normal = repo.dirstate.normallookup
1931 normal = repo.dirstate.normallookup
1931 if node == parent and p2 == nullid:
1932 if node == parent and p2 == nullid:
1932 normal = repo.dirstate.normal
1933 normal = repo.dirstate.normal
1933 for f in undelete[0]:
1934 for f in undelete[0]:
1934 checkout(f)
1935 checkout(f)
1935 normal(f)
1936 normal(f)
1936
1937
1937 if targetsubs:
1938 if targetsubs:
1938 # Revert the subrepos on the revert list
1939 # Revert the subrepos on the revert list
1939 for sub in targetsubs:
1940 for sub in targetsubs:
1940 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1941 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1941 finally:
1942 finally:
1942 wlock.release()
1943 wlock.release()
1943
1944
1944 def command(table):
1945 def command(table):
1945 '''returns a function object bound to table which can be used as
1946 '''returns a function object bound to table which can be used as
1946 a decorator for populating table as a command table'''
1947 a decorator for populating table as a command table'''
1947
1948
1948 def cmd(name, options, synopsis=None):
1949 def cmd(name, options, synopsis=None):
1949 def decorator(func):
1950 def decorator(func):
1950 if synopsis:
1951 if synopsis:
1951 table[name] = func, options[:], synopsis
1952 table[name] = func, options[:], synopsis
1952 else:
1953 else:
1953 table[name] = func, options[:]
1954 table[name] = func, options[:]
1954 return func
1955 return func
1955 return decorator
1956 return decorator
1956
1957
1957 return cmd
1958 return cmd
General Comments 0
You need to be logged in to leave comments. Login now