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