##// END OF EJS Templates
diff: don't crash when diffing a revision with a deleted subrepo (issue3153)...
Renato Cunha -
r15634:cfc15cbe stable
parent child Browse files
Show More
@@ -1,1271 +1,1277 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import subrepo
13 import subrepo
14
14
15 def parsealiases(cmd):
15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|")
16 return cmd.lstrip("^").split("|")
17
17
18 def findpossible(cmd, table, strict=False):
18 def findpossible(cmd, table, strict=False):
19 """
19 """
20 Return cmd -> (aliases, command table entry)
20 Return cmd -> (aliases, command table entry)
21 for each matching command.
21 for each matching command.
22 Return debug commands (or their aliases) only if no normal command matches.
22 Return debug commands (or their aliases) only if no normal command matches.
23 """
23 """
24 choice = {}
24 choice = {}
25 debugchoice = {}
25 debugchoice = {}
26
26
27 if cmd in table:
27 if cmd in table:
28 # short-circuit exact matches, "log" alias beats "^log|history"
28 # short-circuit exact matches, "log" alias beats "^log|history"
29 keys = [cmd]
29 keys = [cmd]
30 else:
30 else:
31 keys = table.keys()
31 keys = table.keys()
32
32
33 for e in keys:
33 for e in keys:
34 aliases = parsealiases(e)
34 aliases = parsealiases(e)
35 found = None
35 found = None
36 if cmd in aliases:
36 if cmd in aliases:
37 found = cmd
37 found = cmd
38 elif not strict:
38 elif not strict:
39 for a in aliases:
39 for a in aliases:
40 if a.startswith(cmd):
40 if a.startswith(cmd):
41 found = a
41 found = a
42 break
42 break
43 if found is not None:
43 if found is not None:
44 if aliases[0].startswith("debug") or found.startswith("debug"):
44 if aliases[0].startswith("debug") or found.startswith("debug"):
45 debugchoice[found] = (aliases, table[e])
45 debugchoice[found] = (aliases, table[e])
46 else:
46 else:
47 choice[found] = (aliases, table[e])
47 choice[found] = (aliases, table[e])
48
48
49 if not choice and debugchoice:
49 if not choice and debugchoice:
50 choice = debugchoice
50 choice = debugchoice
51
51
52 return choice
52 return choice
53
53
54 def findcmd(cmd, table, strict=True):
54 def findcmd(cmd, table, strict=True):
55 """Return (aliases, command table entry) for command string."""
55 """Return (aliases, command table entry) for command string."""
56 choice = findpossible(cmd, table, strict)
56 choice = findpossible(cmd, table, strict)
57
57
58 if cmd in choice:
58 if cmd in choice:
59 return choice[cmd]
59 return choice[cmd]
60
60
61 if len(choice) > 1:
61 if len(choice) > 1:
62 clist = choice.keys()
62 clist = choice.keys()
63 clist.sort()
63 clist.sort()
64 raise error.AmbiguousCommand(cmd, clist)
64 raise error.AmbiguousCommand(cmd, clist)
65
65
66 if choice:
66 if choice:
67 return choice.values()[0]
67 return choice.values()[0]
68
68
69 raise error.UnknownCommand(cmd)
69 raise error.UnknownCommand(cmd)
70
70
71 def findrepo(p):
71 def findrepo(p):
72 while not os.path.isdir(os.path.join(p, ".hg")):
72 while not os.path.isdir(os.path.join(p, ".hg")):
73 oldp, p = p, os.path.dirname(p)
73 oldp, p = p, os.path.dirname(p)
74 if p == oldp:
74 if p == oldp:
75 return None
75 return None
76
76
77 return p
77 return p
78
78
79 def bailifchanged(repo):
79 def bailifchanged(repo):
80 if repo.dirstate.p2() != nullid:
80 if repo.dirstate.p2() != nullid:
81 raise util.Abort(_('outstanding uncommitted merge'))
81 raise util.Abort(_('outstanding uncommitted merge'))
82 modified, added, removed, deleted = repo.status()[:4]
82 modified, added, removed, deleted = repo.status()[:4]
83 if modified or added or removed or deleted:
83 if modified or added or removed or deleted:
84 raise util.Abort(_("outstanding uncommitted changes"))
84 raise util.Abort(_("outstanding uncommitted changes"))
85 ctx = repo[None]
85 ctx = repo[None]
86 for s in ctx.substate:
86 for s in ctx.substate:
87 if ctx.sub(s).dirty():
87 if ctx.sub(s).dirty():
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89
89
90 def logmessage(ui, opts):
90 def logmessage(ui, opts):
91 """ get the log message according to -m and -l option """
91 """ get the log message according to -m and -l option """
92 message = opts.get('message')
92 message = opts.get('message')
93 logfile = opts.get('logfile')
93 logfile = opts.get('logfile')
94
94
95 if message and logfile:
95 if message and logfile:
96 raise util.Abort(_('options --message and --logfile are mutually '
96 raise util.Abort(_('options --message and --logfile are mutually '
97 'exclusive'))
97 'exclusive'))
98 if not message and logfile:
98 if not message and logfile:
99 try:
99 try:
100 if logfile == '-':
100 if logfile == '-':
101 message = ui.fin.read()
101 message = ui.fin.read()
102 else:
102 else:
103 message = '\n'.join(util.readfile(logfile).splitlines())
103 message = '\n'.join(util.readfile(logfile).splitlines())
104 except IOError, inst:
104 except IOError, inst:
105 raise util.Abort(_("can't read commit message '%s': %s") %
105 raise util.Abort(_("can't read commit message '%s': %s") %
106 (logfile, inst.strerror))
106 (logfile, inst.strerror))
107 return message
107 return message
108
108
109 def loglimit(opts):
109 def loglimit(opts):
110 """get the log limit according to option -l/--limit"""
110 """get the log limit according to option -l/--limit"""
111 limit = opts.get('limit')
111 limit = opts.get('limit')
112 if limit:
112 if limit:
113 try:
113 try:
114 limit = int(limit)
114 limit = int(limit)
115 except ValueError:
115 except ValueError:
116 raise util.Abort(_('limit must be a positive integer'))
116 raise util.Abort(_('limit must be a positive integer'))
117 if limit <= 0:
117 if limit <= 0:
118 raise util.Abort(_('limit must be positive'))
118 raise util.Abort(_('limit must be positive'))
119 else:
119 else:
120 limit = None
120 limit = None
121 return limit
121 return limit
122
122
123 def makefilename(repo, pat, node, desc=None,
123 def makefilename(repo, pat, node, desc=None,
124 total=None, seqno=None, revwidth=None, pathname=None):
124 total=None, seqno=None, revwidth=None, pathname=None):
125 node_expander = {
125 node_expander = {
126 'H': lambda: hex(node),
126 'H': lambda: hex(node),
127 'R': lambda: str(repo.changelog.rev(node)),
127 'R': lambda: str(repo.changelog.rev(node)),
128 'h': lambda: short(node),
128 'h': lambda: short(node),
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 }
130 }
131 expander = {
131 expander = {
132 '%': lambda: '%',
132 '%': lambda: '%',
133 'b': lambda: os.path.basename(repo.root),
133 'b': lambda: os.path.basename(repo.root),
134 }
134 }
135
135
136 try:
136 try:
137 if node:
137 if node:
138 expander.update(node_expander)
138 expander.update(node_expander)
139 if node:
139 if node:
140 expander['r'] = (lambda:
140 expander['r'] = (lambda:
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 if total is not None:
142 if total is not None:
143 expander['N'] = lambda: str(total)
143 expander['N'] = lambda: str(total)
144 if seqno is not None:
144 if seqno is not None:
145 expander['n'] = lambda: str(seqno)
145 expander['n'] = lambda: str(seqno)
146 if total is not None and seqno is not None:
146 if total is not None and seqno is not None:
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 if pathname is not None:
148 if pathname is not None:
149 expander['s'] = lambda: os.path.basename(pathname)
149 expander['s'] = lambda: os.path.basename(pathname)
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 expander['p'] = lambda: pathname
151 expander['p'] = lambda: pathname
152
152
153 newname = []
153 newname = []
154 patlen = len(pat)
154 patlen = len(pat)
155 i = 0
155 i = 0
156 while i < patlen:
156 while i < patlen:
157 c = pat[i]
157 c = pat[i]
158 if c == '%':
158 if c == '%':
159 i += 1
159 i += 1
160 c = pat[i]
160 c = pat[i]
161 c = expander[c]()
161 c = expander[c]()
162 newname.append(c)
162 newname.append(c)
163 i += 1
163 i += 1
164 return ''.join(newname)
164 return ''.join(newname)
165 except KeyError, inst:
165 except KeyError, inst:
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 inst.args[0])
167 inst.args[0])
168
168
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 seqno=None, revwidth=None, mode='wb', pathname=None):
170 seqno=None, revwidth=None, mode='wb', pathname=None):
171
171
172 writable = mode not in ('r', 'rb')
172 writable = mode not in ('r', 'rb')
173
173
174 if not pat or pat == '-':
174 if not pat or pat == '-':
175 fp = writable and repo.ui.fout or repo.ui.fin
175 fp = writable and repo.ui.fout or repo.ui.fin
176 if util.safehasattr(fp, 'fileno'):
176 if util.safehasattr(fp, 'fileno'):
177 return os.fdopen(os.dup(fp.fileno()), mode)
177 return os.fdopen(os.dup(fp.fileno()), mode)
178 else:
178 else:
179 # if this fp can't be duped properly, return
179 # if this fp can't be duped properly, return
180 # a dummy object that can be closed
180 # a dummy object that can be closed
181 class wrappedfileobj(object):
181 class wrappedfileobj(object):
182 noop = lambda x: None
182 noop = lambda x: None
183 def __init__(self, f):
183 def __init__(self, f):
184 self.f = f
184 self.f = f
185 def __getattr__(self, attr):
185 def __getattr__(self, attr):
186 if attr == 'close':
186 if attr == 'close':
187 return self.noop
187 return self.noop
188 else:
188 else:
189 return getattr(self.f, attr)
189 return getattr(self.f, attr)
190
190
191 return wrappedfileobj(fp)
191 return wrappedfileobj(fp)
192 if util.safehasattr(pat, 'write') and writable:
192 if util.safehasattr(pat, 'write') and writable:
193 return pat
193 return pat
194 if util.safehasattr(pat, 'read') and 'r' in mode:
194 if util.safehasattr(pat, 'read') and 'r' in mode:
195 return pat
195 return pat
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 pathname),
197 pathname),
198 mode)
198 mode)
199
199
200 def openrevlog(repo, cmd, file_, opts):
200 def openrevlog(repo, cmd, file_, opts):
201 """opens the changelog, manifest, a filelog or a given revlog"""
201 """opens the changelog, manifest, a filelog or a given revlog"""
202 cl = opts['changelog']
202 cl = opts['changelog']
203 mf = opts['manifest']
203 mf = opts['manifest']
204 msg = None
204 msg = None
205 if cl and mf:
205 if cl and mf:
206 msg = _('cannot specify --changelog and --manifest at the same time')
206 msg = _('cannot specify --changelog and --manifest at the same time')
207 elif cl or mf:
207 elif cl or mf:
208 if file_:
208 if file_:
209 msg = _('cannot specify filename with --changelog or --manifest')
209 msg = _('cannot specify filename with --changelog or --manifest')
210 elif not repo:
210 elif not repo:
211 msg = _('cannot specify --changelog or --manifest '
211 msg = _('cannot specify --changelog or --manifest '
212 'without a repository')
212 'without a repository')
213 if msg:
213 if msg:
214 raise util.Abort(msg)
214 raise util.Abort(msg)
215
215
216 r = None
216 r = None
217 if repo:
217 if repo:
218 if cl:
218 if cl:
219 r = repo.changelog
219 r = repo.changelog
220 elif mf:
220 elif mf:
221 r = repo.manifest
221 r = repo.manifest
222 elif file_:
222 elif file_:
223 filelog = repo.file(file_)
223 filelog = repo.file(file_)
224 if len(filelog):
224 if len(filelog):
225 r = filelog
225 r = filelog
226 if not r:
226 if not r:
227 if not file_:
227 if not file_:
228 raise error.CommandError(cmd, _('invalid arguments'))
228 raise error.CommandError(cmd, _('invalid arguments'))
229 if not os.path.isfile(file_):
229 if not os.path.isfile(file_):
230 raise util.Abort(_("revlog '%s' not found") % file_)
230 raise util.Abort(_("revlog '%s' not found") % file_)
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 file_[:-2] + ".i")
232 file_[:-2] + ".i")
233 return r
233 return r
234
234
235 def copy(ui, repo, pats, opts, rename=False):
235 def copy(ui, repo, pats, opts, rename=False):
236 # called with the repo lock held
236 # called with the repo lock held
237 #
237 #
238 # hgsep => pathname that uses "/" to separate directories
238 # hgsep => pathname that uses "/" to separate directories
239 # ossep => pathname that uses os.sep to separate directories
239 # ossep => pathname that uses os.sep to separate directories
240 cwd = repo.getcwd()
240 cwd = repo.getcwd()
241 targets = {}
241 targets = {}
242 after = opts.get("after")
242 after = opts.get("after")
243 dryrun = opts.get("dry_run")
243 dryrun = opts.get("dry_run")
244 wctx = repo[None]
244 wctx = repo[None]
245
245
246 def walkpat(pat):
246 def walkpat(pat):
247 srcs = []
247 srcs = []
248 badstates = after and '?' or '?r'
248 badstates = after and '?' or '?r'
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 for abs in repo.walk(m):
250 for abs in repo.walk(m):
251 state = repo.dirstate[abs]
251 state = repo.dirstate[abs]
252 rel = m.rel(abs)
252 rel = m.rel(abs)
253 exact = m.exact(abs)
253 exact = m.exact(abs)
254 if state in badstates:
254 if state in badstates:
255 if exact and state == '?':
255 if exact and state == '?':
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 if exact and state == 'r':
257 if exact and state == 'r':
258 ui.warn(_('%s: not copying - file has been marked for'
258 ui.warn(_('%s: not copying - file has been marked for'
259 ' remove\n') % rel)
259 ' remove\n') % rel)
260 continue
260 continue
261 # abs: hgsep
261 # abs: hgsep
262 # rel: ossep
262 # rel: ossep
263 srcs.append((abs, rel, exact))
263 srcs.append((abs, rel, exact))
264 return srcs
264 return srcs
265
265
266 # abssrc: hgsep
266 # abssrc: hgsep
267 # relsrc: ossep
267 # relsrc: ossep
268 # otarget: ossep
268 # otarget: ossep
269 def copyfile(abssrc, relsrc, otarget, exact):
269 def copyfile(abssrc, relsrc, otarget, exact):
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 reltarget = repo.pathto(abstarget, cwd)
271 reltarget = repo.pathto(abstarget, cwd)
272 target = repo.wjoin(abstarget)
272 target = repo.wjoin(abstarget)
273 src = repo.wjoin(abssrc)
273 src = repo.wjoin(abssrc)
274 state = repo.dirstate[abstarget]
274 state = repo.dirstate[abstarget]
275
275
276 scmutil.checkportable(ui, abstarget)
276 scmutil.checkportable(ui, abstarget)
277
277
278 # check for collisions
278 # check for collisions
279 prevsrc = targets.get(abstarget)
279 prevsrc = targets.get(abstarget)
280 if prevsrc is not None:
280 if prevsrc is not None:
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
282 (reltarget, repo.pathto(abssrc, cwd),
282 (reltarget, repo.pathto(abssrc, cwd),
283 repo.pathto(prevsrc, cwd)))
283 repo.pathto(prevsrc, cwd)))
284 return
284 return
285
285
286 # check for overwrites
286 # check for overwrites
287 exists = os.path.lexists(target)
287 exists = os.path.lexists(target)
288 if not after and exists or after and state in 'mn':
288 if not after and exists or after and state in 'mn':
289 if not opts['force']:
289 if not opts['force']:
290 ui.warn(_('%s: not overwriting - file exists\n') %
290 ui.warn(_('%s: not overwriting - file exists\n') %
291 reltarget)
291 reltarget)
292 return
292 return
293
293
294 if after:
294 if after:
295 if not exists:
295 if not exists:
296 if rename:
296 if rename:
297 ui.warn(_('%s: not recording move - %s does not exist\n') %
297 ui.warn(_('%s: not recording move - %s does not exist\n') %
298 (relsrc, reltarget))
298 (relsrc, reltarget))
299 else:
299 else:
300 ui.warn(_('%s: not recording copy - %s does not exist\n') %
300 ui.warn(_('%s: not recording copy - %s does not exist\n') %
301 (relsrc, reltarget))
301 (relsrc, reltarget))
302 return
302 return
303 elif not dryrun:
303 elif not dryrun:
304 try:
304 try:
305 if exists:
305 if exists:
306 os.unlink(target)
306 os.unlink(target)
307 targetdir = os.path.dirname(target) or '.'
307 targetdir = os.path.dirname(target) or '.'
308 if not os.path.isdir(targetdir):
308 if not os.path.isdir(targetdir):
309 os.makedirs(targetdir)
309 os.makedirs(targetdir)
310 util.copyfile(src, target)
310 util.copyfile(src, target)
311 srcexists = True
311 srcexists = True
312 except IOError, inst:
312 except IOError, inst:
313 if inst.errno == errno.ENOENT:
313 if inst.errno == errno.ENOENT:
314 ui.warn(_('%s: deleted in working copy\n') % relsrc)
314 ui.warn(_('%s: deleted in working copy\n') % relsrc)
315 srcexists = False
315 srcexists = False
316 else:
316 else:
317 ui.warn(_('%s: cannot copy - %s\n') %
317 ui.warn(_('%s: cannot copy - %s\n') %
318 (relsrc, inst.strerror))
318 (relsrc, inst.strerror))
319 return True # report a failure
319 return True # report a failure
320
320
321 if ui.verbose or not exact:
321 if ui.verbose or not exact:
322 if rename:
322 if rename:
323 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
323 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
324 else:
324 else:
325 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
325 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
326
326
327 targets[abstarget] = abssrc
327 targets[abstarget] = abssrc
328
328
329 # fix up dirstate
329 # fix up dirstate
330 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
330 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
331 dryrun=dryrun, cwd=cwd)
331 dryrun=dryrun, cwd=cwd)
332 if rename and not dryrun:
332 if rename and not dryrun:
333 if not after and srcexists:
333 if not after and srcexists:
334 util.unlinkpath(repo.wjoin(abssrc))
334 util.unlinkpath(repo.wjoin(abssrc))
335 wctx.forget([abssrc])
335 wctx.forget([abssrc])
336
336
337 # pat: ossep
337 # pat: ossep
338 # dest ossep
338 # dest ossep
339 # srcs: list of (hgsep, hgsep, ossep, bool)
339 # srcs: list of (hgsep, hgsep, ossep, bool)
340 # return: function that takes hgsep and returns ossep
340 # return: function that takes hgsep and returns ossep
341 def targetpathfn(pat, dest, srcs):
341 def targetpathfn(pat, dest, srcs):
342 if os.path.isdir(pat):
342 if os.path.isdir(pat):
343 abspfx = scmutil.canonpath(repo.root, cwd, pat)
343 abspfx = scmutil.canonpath(repo.root, cwd, pat)
344 abspfx = util.localpath(abspfx)
344 abspfx = util.localpath(abspfx)
345 if destdirexists:
345 if destdirexists:
346 striplen = len(os.path.split(abspfx)[0])
346 striplen = len(os.path.split(abspfx)[0])
347 else:
347 else:
348 striplen = len(abspfx)
348 striplen = len(abspfx)
349 if striplen:
349 if striplen:
350 striplen += len(os.sep)
350 striplen += len(os.sep)
351 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
351 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
352 elif destdirexists:
352 elif destdirexists:
353 res = lambda p: os.path.join(dest,
353 res = lambda p: os.path.join(dest,
354 os.path.basename(util.localpath(p)))
354 os.path.basename(util.localpath(p)))
355 else:
355 else:
356 res = lambda p: dest
356 res = lambda p: dest
357 return res
357 return res
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 targetpathafterfn(pat, dest, srcs):
363 def targetpathafterfn(pat, dest, srcs):
364 if matchmod.patkind(pat):
364 if matchmod.patkind(pat):
365 # a mercurial pattern
365 # a mercurial pattern
366 res = lambda p: os.path.join(dest,
366 res = lambda p: os.path.join(dest,
367 os.path.basename(util.localpath(p)))
367 os.path.basename(util.localpath(p)))
368 else:
368 else:
369 abspfx = scmutil.canonpath(repo.root, cwd, pat)
369 abspfx = scmutil.canonpath(repo.root, cwd, pat)
370 if len(abspfx) < len(srcs[0][0]):
370 if len(abspfx) < len(srcs[0][0]):
371 # A directory. Either the target path contains the last
371 # A directory. Either the target path contains the last
372 # component of the source path or it does not.
372 # component of the source path or it does not.
373 def evalpath(striplen):
373 def evalpath(striplen):
374 score = 0
374 score = 0
375 for s in srcs:
375 for s in srcs:
376 t = os.path.join(dest, util.localpath(s[0])[striplen:])
376 t = os.path.join(dest, util.localpath(s[0])[striplen:])
377 if os.path.lexists(t):
377 if os.path.lexists(t):
378 score += 1
378 score += 1
379 return score
379 return score
380
380
381 abspfx = util.localpath(abspfx)
381 abspfx = util.localpath(abspfx)
382 striplen = len(abspfx)
382 striplen = len(abspfx)
383 if striplen:
383 if striplen:
384 striplen += len(os.sep)
384 striplen += len(os.sep)
385 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
385 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
386 score = evalpath(striplen)
386 score = evalpath(striplen)
387 striplen1 = len(os.path.split(abspfx)[0])
387 striplen1 = len(os.path.split(abspfx)[0])
388 if striplen1:
388 if striplen1:
389 striplen1 += len(os.sep)
389 striplen1 += len(os.sep)
390 if evalpath(striplen1) > score:
390 if evalpath(striplen1) > score:
391 striplen = striplen1
391 striplen = striplen1
392 res = lambda p: os.path.join(dest,
392 res = lambda p: os.path.join(dest,
393 util.localpath(p)[striplen:])
393 util.localpath(p)[striplen:])
394 else:
394 else:
395 # a file
395 # a file
396 if destdirexists:
396 if destdirexists:
397 res = lambda p: os.path.join(dest,
397 res = lambda p: os.path.join(dest,
398 os.path.basename(util.localpath(p)))
398 os.path.basename(util.localpath(p)))
399 else:
399 else:
400 res = lambda p: dest
400 res = lambda p: dest
401 return res
401 return res
402
402
403
403
404 pats = scmutil.expandpats(pats)
404 pats = scmutil.expandpats(pats)
405 if not pats:
405 if not pats:
406 raise util.Abort(_('no source or destination specified'))
406 raise util.Abort(_('no source or destination specified'))
407 if len(pats) == 1:
407 if len(pats) == 1:
408 raise util.Abort(_('no destination specified'))
408 raise util.Abort(_('no destination specified'))
409 dest = pats.pop()
409 dest = pats.pop()
410 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
410 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
411 if not destdirexists:
411 if not destdirexists:
412 if len(pats) > 1 or matchmod.patkind(pats[0]):
412 if len(pats) > 1 or matchmod.patkind(pats[0]):
413 raise util.Abort(_('with multiple sources, destination must be an '
413 raise util.Abort(_('with multiple sources, destination must be an '
414 'existing directory'))
414 'existing directory'))
415 if util.endswithsep(dest):
415 if util.endswithsep(dest):
416 raise util.Abort(_('destination %s is not a directory') % dest)
416 raise util.Abort(_('destination %s is not a directory') % dest)
417
417
418 tfn = targetpathfn
418 tfn = targetpathfn
419 if after:
419 if after:
420 tfn = targetpathafterfn
420 tfn = targetpathafterfn
421 copylist = []
421 copylist = []
422 for pat in pats:
422 for pat in pats:
423 srcs = walkpat(pat)
423 srcs = walkpat(pat)
424 if not srcs:
424 if not srcs:
425 continue
425 continue
426 copylist.append((tfn(pat, dest, srcs), srcs))
426 copylist.append((tfn(pat, dest, srcs), srcs))
427 if not copylist:
427 if not copylist:
428 raise util.Abort(_('no files to copy'))
428 raise util.Abort(_('no files to copy'))
429
429
430 errors = 0
430 errors = 0
431 for targetpath, srcs in copylist:
431 for targetpath, srcs in copylist:
432 for abssrc, relsrc, exact in srcs:
432 for abssrc, relsrc, exact in srcs:
433 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
433 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
434 errors += 1
434 errors += 1
435
435
436 if errors:
436 if errors:
437 ui.warn(_('(consider using --after)\n'))
437 ui.warn(_('(consider using --after)\n'))
438
438
439 return errors != 0
439 return errors != 0
440
440
441 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
441 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
442 runargs=None, appendpid=False):
442 runargs=None, appendpid=False):
443 '''Run a command as a service.'''
443 '''Run a command as a service.'''
444
444
445 if opts['daemon'] and not opts['daemon_pipefds']:
445 if opts['daemon'] and not opts['daemon_pipefds']:
446 # Signal child process startup with file removal
446 # Signal child process startup with file removal
447 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
447 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
448 os.close(lockfd)
448 os.close(lockfd)
449 try:
449 try:
450 if not runargs:
450 if not runargs:
451 runargs = util.hgcmd() + sys.argv[1:]
451 runargs = util.hgcmd() + sys.argv[1:]
452 runargs.append('--daemon-pipefds=%s' % lockpath)
452 runargs.append('--daemon-pipefds=%s' % lockpath)
453 # Don't pass --cwd to the child process, because we've already
453 # Don't pass --cwd to the child process, because we've already
454 # changed directory.
454 # changed directory.
455 for i in xrange(1, len(runargs)):
455 for i in xrange(1, len(runargs)):
456 if runargs[i].startswith('--cwd='):
456 if runargs[i].startswith('--cwd='):
457 del runargs[i]
457 del runargs[i]
458 break
458 break
459 elif runargs[i].startswith('--cwd'):
459 elif runargs[i].startswith('--cwd'):
460 del runargs[i:i + 2]
460 del runargs[i:i + 2]
461 break
461 break
462 def condfn():
462 def condfn():
463 return not os.path.exists(lockpath)
463 return not os.path.exists(lockpath)
464 pid = util.rundetached(runargs, condfn)
464 pid = util.rundetached(runargs, condfn)
465 if pid < 0:
465 if pid < 0:
466 raise util.Abort(_('child process failed to start'))
466 raise util.Abort(_('child process failed to start'))
467 finally:
467 finally:
468 try:
468 try:
469 os.unlink(lockpath)
469 os.unlink(lockpath)
470 except OSError, e:
470 except OSError, e:
471 if e.errno != errno.ENOENT:
471 if e.errno != errno.ENOENT:
472 raise
472 raise
473 if parentfn:
473 if parentfn:
474 return parentfn(pid)
474 return parentfn(pid)
475 else:
475 else:
476 return
476 return
477
477
478 if initfn:
478 if initfn:
479 initfn()
479 initfn()
480
480
481 if opts['pid_file']:
481 if opts['pid_file']:
482 mode = appendpid and 'a' or 'w'
482 mode = appendpid and 'a' or 'w'
483 fp = open(opts['pid_file'], mode)
483 fp = open(opts['pid_file'], mode)
484 fp.write(str(os.getpid()) + '\n')
484 fp.write(str(os.getpid()) + '\n')
485 fp.close()
485 fp.close()
486
486
487 if opts['daemon_pipefds']:
487 if opts['daemon_pipefds']:
488 lockpath = opts['daemon_pipefds']
488 lockpath = opts['daemon_pipefds']
489 try:
489 try:
490 os.setsid()
490 os.setsid()
491 except AttributeError:
491 except AttributeError:
492 pass
492 pass
493 os.unlink(lockpath)
493 os.unlink(lockpath)
494 util.hidewindow()
494 util.hidewindow()
495 sys.stdout.flush()
495 sys.stdout.flush()
496 sys.stderr.flush()
496 sys.stderr.flush()
497
497
498 nullfd = os.open(util.nulldev, os.O_RDWR)
498 nullfd = os.open(util.nulldev, os.O_RDWR)
499 logfilefd = nullfd
499 logfilefd = nullfd
500 if logfile:
500 if logfile:
501 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
501 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
502 os.dup2(nullfd, 0)
502 os.dup2(nullfd, 0)
503 os.dup2(logfilefd, 1)
503 os.dup2(logfilefd, 1)
504 os.dup2(logfilefd, 2)
504 os.dup2(logfilefd, 2)
505 if nullfd not in (0, 1, 2):
505 if nullfd not in (0, 1, 2):
506 os.close(nullfd)
506 os.close(nullfd)
507 if logfile and logfilefd not in (0, 1, 2):
507 if logfile and logfilefd not in (0, 1, 2):
508 os.close(logfilefd)
508 os.close(logfilefd)
509
509
510 if runfn:
510 if runfn:
511 return runfn()
511 return runfn()
512
512
513 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
513 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
514 opts=None):
514 opts=None):
515 '''export changesets as hg patches.'''
515 '''export changesets as hg patches.'''
516
516
517 total = len(revs)
517 total = len(revs)
518 revwidth = max([len(str(rev)) for rev in revs])
518 revwidth = max([len(str(rev)) for rev in revs])
519
519
520 def single(rev, seqno, fp):
520 def single(rev, seqno, fp):
521 ctx = repo[rev]
521 ctx = repo[rev]
522 node = ctx.node()
522 node = ctx.node()
523 parents = [p.node() for p in ctx.parents() if p]
523 parents = [p.node() for p in ctx.parents() if p]
524 branch = ctx.branch()
524 branch = ctx.branch()
525 if switch_parent:
525 if switch_parent:
526 parents.reverse()
526 parents.reverse()
527 prev = (parents and parents[0]) or nullid
527 prev = (parents and parents[0]) or nullid
528
528
529 shouldclose = False
529 shouldclose = False
530 if not fp:
530 if not fp:
531 desc_lines = ctx.description().rstrip().split('\n')
531 desc_lines = ctx.description().rstrip().split('\n')
532 desc = desc_lines[0] #Commit always has a first line.
532 desc = desc_lines[0] #Commit always has a first line.
533 fp = makefileobj(repo, template, node, desc=desc, total=total,
533 fp = makefileobj(repo, template, node, desc=desc, total=total,
534 seqno=seqno, revwidth=revwidth, mode='ab')
534 seqno=seqno, revwidth=revwidth, mode='ab')
535 if fp != template:
535 if fp != template:
536 shouldclose = True
536 shouldclose = True
537 if fp != sys.stdout and util.safehasattr(fp, 'name'):
537 if fp != sys.stdout and util.safehasattr(fp, 'name'):
538 repo.ui.note("%s\n" % fp.name)
538 repo.ui.note("%s\n" % fp.name)
539
539
540 fp.write("# HG changeset patch\n")
540 fp.write("# HG changeset patch\n")
541 fp.write("# User %s\n" % ctx.user())
541 fp.write("# User %s\n" % ctx.user())
542 fp.write("# Date %d %d\n" % ctx.date())
542 fp.write("# Date %d %d\n" % ctx.date())
543 if branch and branch != 'default':
543 if branch and branch != 'default':
544 fp.write("# Branch %s\n" % branch)
544 fp.write("# Branch %s\n" % branch)
545 fp.write("# Node ID %s\n" % hex(node))
545 fp.write("# Node ID %s\n" % hex(node))
546 fp.write("# Parent %s\n" % hex(prev))
546 fp.write("# Parent %s\n" % hex(prev))
547 if len(parents) > 1:
547 if len(parents) > 1:
548 fp.write("# Parent %s\n" % hex(parents[1]))
548 fp.write("# Parent %s\n" % hex(parents[1]))
549 fp.write(ctx.description().rstrip())
549 fp.write(ctx.description().rstrip())
550 fp.write("\n\n")
550 fp.write("\n\n")
551
551
552 for chunk in patch.diff(repo, prev, node, opts=opts):
552 for chunk in patch.diff(repo, prev, node, opts=opts):
553 fp.write(chunk)
553 fp.write(chunk)
554
554
555 if shouldclose:
555 if shouldclose:
556 fp.close()
556 fp.close()
557
557
558 for seqno, rev in enumerate(revs):
558 for seqno, rev in enumerate(revs):
559 single(rev, seqno + 1, fp)
559 single(rev, seqno + 1, fp)
560
560
561 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
561 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
562 changes=None, stat=False, fp=None, prefix='',
562 changes=None, stat=False, fp=None, prefix='',
563 listsubrepos=False):
563 listsubrepos=False):
564 '''show diff or diffstat.'''
564 '''show diff or diffstat.'''
565 if fp is None:
565 if fp is None:
566 write = ui.write
566 write = ui.write
567 else:
567 else:
568 def write(s, **kw):
568 def write(s, **kw):
569 fp.write(s)
569 fp.write(s)
570
570
571 if stat:
571 if stat:
572 diffopts = diffopts.copy(context=0)
572 diffopts = diffopts.copy(context=0)
573 width = 80
573 width = 80
574 if not ui.plain():
574 if not ui.plain():
575 width = ui.termwidth()
575 width = ui.termwidth()
576 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
576 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
577 prefix=prefix)
577 prefix=prefix)
578 for chunk, label in patch.diffstatui(util.iterlines(chunks),
578 for chunk, label in patch.diffstatui(util.iterlines(chunks),
579 width=width,
579 width=width,
580 git=diffopts.git):
580 git=diffopts.git):
581 write(chunk, label=label)
581 write(chunk, label=label)
582 else:
582 else:
583 for chunk, label in patch.diffui(repo, node1, node2, match,
583 for chunk, label in patch.diffui(repo, node1, node2, match,
584 changes, diffopts, prefix=prefix):
584 changes, diffopts, prefix=prefix):
585 write(chunk, label=label)
585 write(chunk, label=label)
586
586
587 if listsubrepos:
587 if listsubrepos:
588 ctx1 = repo[node1]
588 ctx1 = repo[node1]
589 ctx2 = repo[node2]
589 ctx2 = repo[node2]
590 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
590 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
591 if node2 is not None:
591 try:
592 node2 = ctx2.substate[subpath][1]
592 if node2 is not None:
593 node2 = ctx2.substate[subpath][1]
594 except KeyError:
595 # A subrepo that existed in node1 was deleted between node1 and
596 # node2 (inclusive). Thus, ctx2's substate won't contain that
597 # subpath. The best we can do is to ignore it.
598 node2 = None
593 submatch = matchmod.narrowmatcher(subpath, match)
599 submatch = matchmod.narrowmatcher(subpath, match)
594 sub.diff(diffopts, node2, submatch, changes=changes,
600 sub.diff(diffopts, node2, submatch, changes=changes,
595 stat=stat, fp=fp, prefix=prefix)
601 stat=stat, fp=fp, prefix=prefix)
596
602
597 class changeset_printer(object):
603 class changeset_printer(object):
598 '''show changeset information when templating not requested.'''
604 '''show changeset information when templating not requested.'''
599
605
600 def __init__(self, ui, repo, patch, diffopts, buffered):
606 def __init__(self, ui, repo, patch, diffopts, buffered):
601 self.ui = ui
607 self.ui = ui
602 self.repo = repo
608 self.repo = repo
603 self.buffered = buffered
609 self.buffered = buffered
604 self.patch = patch
610 self.patch = patch
605 self.diffopts = diffopts
611 self.diffopts = diffopts
606 self.header = {}
612 self.header = {}
607 self.hunk = {}
613 self.hunk = {}
608 self.lastheader = None
614 self.lastheader = None
609 self.footer = None
615 self.footer = None
610
616
611 def flush(self, rev):
617 def flush(self, rev):
612 if rev in self.header:
618 if rev in self.header:
613 h = self.header[rev]
619 h = self.header[rev]
614 if h != self.lastheader:
620 if h != self.lastheader:
615 self.lastheader = h
621 self.lastheader = h
616 self.ui.write(h)
622 self.ui.write(h)
617 del self.header[rev]
623 del self.header[rev]
618 if rev in self.hunk:
624 if rev in self.hunk:
619 self.ui.write(self.hunk[rev])
625 self.ui.write(self.hunk[rev])
620 del self.hunk[rev]
626 del self.hunk[rev]
621 return 1
627 return 1
622 return 0
628 return 0
623
629
624 def close(self):
630 def close(self):
625 if self.footer:
631 if self.footer:
626 self.ui.write(self.footer)
632 self.ui.write(self.footer)
627
633
628 def show(self, ctx, copies=None, matchfn=None, **props):
634 def show(self, ctx, copies=None, matchfn=None, **props):
629 if self.buffered:
635 if self.buffered:
630 self.ui.pushbuffer()
636 self.ui.pushbuffer()
631 self._show(ctx, copies, matchfn, props)
637 self._show(ctx, copies, matchfn, props)
632 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
638 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
633 else:
639 else:
634 self._show(ctx, copies, matchfn, props)
640 self._show(ctx, copies, matchfn, props)
635
641
636 def _show(self, ctx, copies, matchfn, props):
642 def _show(self, ctx, copies, matchfn, props):
637 '''show a single changeset or file revision'''
643 '''show a single changeset or file revision'''
638 changenode = ctx.node()
644 changenode = ctx.node()
639 rev = ctx.rev()
645 rev = ctx.rev()
640
646
641 if self.ui.quiet:
647 if self.ui.quiet:
642 self.ui.write("%d:%s\n" % (rev, short(changenode)),
648 self.ui.write("%d:%s\n" % (rev, short(changenode)),
643 label='log.node')
649 label='log.node')
644 return
650 return
645
651
646 log = self.repo.changelog
652 log = self.repo.changelog
647 date = util.datestr(ctx.date())
653 date = util.datestr(ctx.date())
648
654
649 hexfunc = self.ui.debugflag and hex or short
655 hexfunc = self.ui.debugflag and hex or short
650
656
651 parents = [(p, hexfunc(log.node(p)))
657 parents = [(p, hexfunc(log.node(p)))
652 for p in self._meaningful_parentrevs(log, rev)]
658 for p in self._meaningful_parentrevs(log, rev)]
653
659
654 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
660 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
655 label='log.changeset')
661 label='log.changeset')
656
662
657 branch = ctx.branch()
663 branch = ctx.branch()
658 # don't show the default branch name
664 # don't show the default branch name
659 if branch != 'default':
665 if branch != 'default':
660 self.ui.write(_("branch: %s\n") % branch,
666 self.ui.write(_("branch: %s\n") % branch,
661 label='log.branch')
667 label='log.branch')
662 for bookmark in self.repo.nodebookmarks(changenode):
668 for bookmark in self.repo.nodebookmarks(changenode):
663 self.ui.write(_("bookmark: %s\n") % bookmark,
669 self.ui.write(_("bookmark: %s\n") % bookmark,
664 label='log.bookmark')
670 label='log.bookmark')
665 for tag in self.repo.nodetags(changenode):
671 for tag in self.repo.nodetags(changenode):
666 self.ui.write(_("tag: %s\n") % tag,
672 self.ui.write(_("tag: %s\n") % tag,
667 label='log.tag')
673 label='log.tag')
668 for parent in parents:
674 for parent in parents:
669 self.ui.write(_("parent: %d:%s\n") % parent,
675 self.ui.write(_("parent: %d:%s\n") % parent,
670 label='log.parent')
676 label='log.parent')
671
677
672 if self.ui.debugflag:
678 if self.ui.debugflag:
673 mnode = ctx.manifestnode()
679 mnode = ctx.manifestnode()
674 self.ui.write(_("manifest: %d:%s\n") %
680 self.ui.write(_("manifest: %d:%s\n") %
675 (self.repo.manifest.rev(mnode), hex(mnode)),
681 (self.repo.manifest.rev(mnode), hex(mnode)),
676 label='ui.debug log.manifest')
682 label='ui.debug log.manifest')
677 self.ui.write(_("user: %s\n") % ctx.user(),
683 self.ui.write(_("user: %s\n") % ctx.user(),
678 label='log.user')
684 label='log.user')
679 self.ui.write(_("date: %s\n") % date,
685 self.ui.write(_("date: %s\n") % date,
680 label='log.date')
686 label='log.date')
681
687
682 if self.ui.debugflag:
688 if self.ui.debugflag:
683 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
689 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
684 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
690 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
685 files):
691 files):
686 if value:
692 if value:
687 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
693 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
688 label='ui.debug log.files')
694 label='ui.debug log.files')
689 elif ctx.files() and self.ui.verbose:
695 elif ctx.files() and self.ui.verbose:
690 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
696 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
691 label='ui.note log.files')
697 label='ui.note log.files')
692 if copies and self.ui.verbose:
698 if copies and self.ui.verbose:
693 copies = ['%s (%s)' % c for c in copies]
699 copies = ['%s (%s)' % c for c in copies]
694 self.ui.write(_("copies: %s\n") % ' '.join(copies),
700 self.ui.write(_("copies: %s\n") % ' '.join(copies),
695 label='ui.note log.copies')
701 label='ui.note log.copies')
696
702
697 extra = ctx.extra()
703 extra = ctx.extra()
698 if extra and self.ui.debugflag:
704 if extra and self.ui.debugflag:
699 for key, value in sorted(extra.items()):
705 for key, value in sorted(extra.items()):
700 self.ui.write(_("extra: %s=%s\n")
706 self.ui.write(_("extra: %s=%s\n")
701 % (key, value.encode('string_escape')),
707 % (key, value.encode('string_escape')),
702 label='ui.debug log.extra')
708 label='ui.debug log.extra')
703
709
704 description = ctx.description().strip()
710 description = ctx.description().strip()
705 if description:
711 if description:
706 if self.ui.verbose:
712 if self.ui.verbose:
707 self.ui.write(_("description:\n"),
713 self.ui.write(_("description:\n"),
708 label='ui.note log.description')
714 label='ui.note log.description')
709 self.ui.write(description,
715 self.ui.write(description,
710 label='ui.note log.description')
716 label='ui.note log.description')
711 self.ui.write("\n\n")
717 self.ui.write("\n\n")
712 else:
718 else:
713 self.ui.write(_("summary: %s\n") %
719 self.ui.write(_("summary: %s\n") %
714 description.splitlines()[0],
720 description.splitlines()[0],
715 label='log.summary')
721 label='log.summary')
716 self.ui.write("\n")
722 self.ui.write("\n")
717
723
718 self.showpatch(changenode, matchfn)
724 self.showpatch(changenode, matchfn)
719
725
720 def showpatch(self, node, matchfn):
726 def showpatch(self, node, matchfn):
721 if not matchfn:
727 if not matchfn:
722 matchfn = self.patch
728 matchfn = self.patch
723 if matchfn:
729 if matchfn:
724 stat = self.diffopts.get('stat')
730 stat = self.diffopts.get('stat')
725 diff = self.diffopts.get('patch')
731 diff = self.diffopts.get('patch')
726 diffopts = patch.diffopts(self.ui, self.diffopts)
732 diffopts = patch.diffopts(self.ui, self.diffopts)
727 prev = self.repo.changelog.parents(node)[0]
733 prev = self.repo.changelog.parents(node)[0]
728 if stat:
734 if stat:
729 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
735 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
730 match=matchfn, stat=True)
736 match=matchfn, stat=True)
731 if diff:
737 if diff:
732 if stat:
738 if stat:
733 self.ui.write("\n")
739 self.ui.write("\n")
734 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
740 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
735 match=matchfn, stat=False)
741 match=matchfn, stat=False)
736 self.ui.write("\n")
742 self.ui.write("\n")
737
743
738 def _meaningful_parentrevs(self, log, rev):
744 def _meaningful_parentrevs(self, log, rev):
739 """Return list of meaningful (or all if debug) parentrevs for rev.
745 """Return list of meaningful (or all if debug) parentrevs for rev.
740
746
741 For merges (two non-nullrev revisions) both parents are meaningful.
747 For merges (two non-nullrev revisions) both parents are meaningful.
742 Otherwise the first parent revision is considered meaningful if it
748 Otherwise the first parent revision is considered meaningful if it
743 is not the preceding revision.
749 is not the preceding revision.
744 """
750 """
745 parents = log.parentrevs(rev)
751 parents = log.parentrevs(rev)
746 if not self.ui.debugflag and parents[1] == nullrev:
752 if not self.ui.debugflag and parents[1] == nullrev:
747 if parents[0] >= rev - 1:
753 if parents[0] >= rev - 1:
748 parents = []
754 parents = []
749 else:
755 else:
750 parents = [parents[0]]
756 parents = [parents[0]]
751 return parents
757 return parents
752
758
753
759
754 class changeset_templater(changeset_printer):
760 class changeset_templater(changeset_printer):
755 '''format changeset information.'''
761 '''format changeset information.'''
756
762
757 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
763 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
758 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
764 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
759 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
765 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
760 defaulttempl = {
766 defaulttempl = {
761 'parent': '{rev}:{node|formatnode} ',
767 'parent': '{rev}:{node|formatnode} ',
762 'manifest': '{rev}:{node|formatnode}',
768 'manifest': '{rev}:{node|formatnode}',
763 'file_copy': '{name} ({source})',
769 'file_copy': '{name} ({source})',
764 'extra': '{key}={value|stringescape}'
770 'extra': '{key}={value|stringescape}'
765 }
771 }
766 # filecopy is preserved for compatibility reasons
772 # filecopy is preserved for compatibility reasons
767 defaulttempl['filecopy'] = defaulttempl['file_copy']
773 defaulttempl['filecopy'] = defaulttempl['file_copy']
768 self.t = templater.templater(mapfile, {'formatnode': formatnode},
774 self.t = templater.templater(mapfile, {'formatnode': formatnode},
769 cache=defaulttempl)
775 cache=defaulttempl)
770 self.cache = {}
776 self.cache = {}
771
777
772 def use_template(self, t):
778 def use_template(self, t):
773 '''set template string to use'''
779 '''set template string to use'''
774 self.t.cache['changeset'] = t
780 self.t.cache['changeset'] = t
775
781
776 def _meaningful_parentrevs(self, ctx):
782 def _meaningful_parentrevs(self, ctx):
777 """Return list of meaningful (or all if debug) parentrevs for rev.
783 """Return list of meaningful (or all if debug) parentrevs for rev.
778 """
784 """
779 parents = ctx.parents()
785 parents = ctx.parents()
780 if len(parents) > 1:
786 if len(parents) > 1:
781 return parents
787 return parents
782 if self.ui.debugflag:
788 if self.ui.debugflag:
783 return [parents[0], self.repo['null']]
789 return [parents[0], self.repo['null']]
784 if parents[0].rev() >= ctx.rev() - 1:
790 if parents[0].rev() >= ctx.rev() - 1:
785 return []
791 return []
786 return parents
792 return parents
787
793
788 def _show(self, ctx, copies, matchfn, props):
794 def _show(self, ctx, copies, matchfn, props):
789 '''show a single changeset or file revision'''
795 '''show a single changeset or file revision'''
790
796
791 showlist = templatekw.showlist
797 showlist = templatekw.showlist
792
798
793 # showparents() behaviour depends on ui trace level which
799 # showparents() behaviour depends on ui trace level which
794 # causes unexpected behaviours at templating level and makes
800 # causes unexpected behaviours at templating level and makes
795 # it harder to extract it in a standalone function. Its
801 # it harder to extract it in a standalone function. Its
796 # behaviour cannot be changed so leave it here for now.
802 # behaviour cannot be changed so leave it here for now.
797 def showparents(**args):
803 def showparents(**args):
798 ctx = args['ctx']
804 ctx = args['ctx']
799 parents = [[('rev', p.rev()), ('node', p.hex())]
805 parents = [[('rev', p.rev()), ('node', p.hex())]
800 for p in self._meaningful_parentrevs(ctx)]
806 for p in self._meaningful_parentrevs(ctx)]
801 return showlist('parent', parents, **args)
807 return showlist('parent', parents, **args)
802
808
803 props = props.copy()
809 props = props.copy()
804 props.update(templatekw.keywords)
810 props.update(templatekw.keywords)
805 props['parents'] = showparents
811 props['parents'] = showparents
806 props['templ'] = self.t
812 props['templ'] = self.t
807 props['ctx'] = ctx
813 props['ctx'] = ctx
808 props['repo'] = self.repo
814 props['repo'] = self.repo
809 props['revcache'] = {'copies': copies}
815 props['revcache'] = {'copies': copies}
810 props['cache'] = self.cache
816 props['cache'] = self.cache
811
817
812 # find correct templates for current mode
818 # find correct templates for current mode
813
819
814 tmplmodes = [
820 tmplmodes = [
815 (True, None),
821 (True, None),
816 (self.ui.verbose, 'verbose'),
822 (self.ui.verbose, 'verbose'),
817 (self.ui.quiet, 'quiet'),
823 (self.ui.quiet, 'quiet'),
818 (self.ui.debugflag, 'debug'),
824 (self.ui.debugflag, 'debug'),
819 ]
825 ]
820
826
821 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
827 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
822 for mode, postfix in tmplmodes:
828 for mode, postfix in tmplmodes:
823 for type in types:
829 for type in types:
824 cur = postfix and ('%s_%s' % (type, postfix)) or type
830 cur = postfix and ('%s_%s' % (type, postfix)) or type
825 if mode and cur in self.t:
831 if mode and cur in self.t:
826 types[type] = cur
832 types[type] = cur
827
833
828 try:
834 try:
829
835
830 # write header
836 # write header
831 if types['header']:
837 if types['header']:
832 h = templater.stringify(self.t(types['header'], **props))
838 h = templater.stringify(self.t(types['header'], **props))
833 if self.buffered:
839 if self.buffered:
834 self.header[ctx.rev()] = h
840 self.header[ctx.rev()] = h
835 else:
841 else:
836 if self.lastheader != h:
842 if self.lastheader != h:
837 self.lastheader = h
843 self.lastheader = h
838 self.ui.write(h)
844 self.ui.write(h)
839
845
840 # write changeset metadata, then patch if requested
846 # write changeset metadata, then patch if requested
841 key = types['changeset']
847 key = types['changeset']
842 self.ui.write(templater.stringify(self.t(key, **props)))
848 self.ui.write(templater.stringify(self.t(key, **props)))
843 self.showpatch(ctx.node(), matchfn)
849 self.showpatch(ctx.node(), matchfn)
844
850
845 if types['footer']:
851 if types['footer']:
846 if not self.footer:
852 if not self.footer:
847 self.footer = templater.stringify(self.t(types['footer'],
853 self.footer = templater.stringify(self.t(types['footer'],
848 **props))
854 **props))
849
855
850 except KeyError, inst:
856 except KeyError, inst:
851 msg = _("%s: no key named '%s'")
857 msg = _("%s: no key named '%s'")
852 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
858 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
853 except SyntaxError, inst:
859 except SyntaxError, inst:
854 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
860 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
855
861
856 def show_changeset(ui, repo, opts, buffered=False):
862 def show_changeset(ui, repo, opts, buffered=False):
857 """show one changeset using template or regular display.
863 """show one changeset using template or regular display.
858
864
859 Display format will be the first non-empty hit of:
865 Display format will be the first non-empty hit of:
860 1. option 'template'
866 1. option 'template'
861 2. option 'style'
867 2. option 'style'
862 3. [ui] setting 'logtemplate'
868 3. [ui] setting 'logtemplate'
863 4. [ui] setting 'style'
869 4. [ui] setting 'style'
864 If all of these values are either the unset or the empty string,
870 If all of these values are either the unset or the empty string,
865 regular display via changeset_printer() is done.
871 regular display via changeset_printer() is done.
866 """
872 """
867 # options
873 # options
868 patch = False
874 patch = False
869 if opts.get('patch') or opts.get('stat'):
875 if opts.get('patch') or opts.get('stat'):
870 patch = scmutil.matchall(repo)
876 patch = scmutil.matchall(repo)
871
877
872 tmpl = opts.get('template')
878 tmpl = opts.get('template')
873 style = None
879 style = None
874 if tmpl:
880 if tmpl:
875 tmpl = templater.parsestring(tmpl, quoted=False)
881 tmpl = templater.parsestring(tmpl, quoted=False)
876 else:
882 else:
877 style = opts.get('style')
883 style = opts.get('style')
878
884
879 # ui settings
885 # ui settings
880 if not (tmpl or style):
886 if not (tmpl or style):
881 tmpl = ui.config('ui', 'logtemplate')
887 tmpl = ui.config('ui', 'logtemplate')
882 if tmpl:
888 if tmpl:
883 tmpl = templater.parsestring(tmpl)
889 tmpl = templater.parsestring(tmpl)
884 else:
890 else:
885 style = util.expandpath(ui.config('ui', 'style', ''))
891 style = util.expandpath(ui.config('ui', 'style', ''))
886
892
887 if not (tmpl or style):
893 if not (tmpl or style):
888 return changeset_printer(ui, repo, patch, opts, buffered)
894 return changeset_printer(ui, repo, patch, opts, buffered)
889
895
890 mapfile = None
896 mapfile = None
891 if style and not tmpl:
897 if style and not tmpl:
892 mapfile = style
898 mapfile = style
893 if not os.path.split(mapfile)[0]:
899 if not os.path.split(mapfile)[0]:
894 mapname = (templater.templatepath('map-cmdline.' + mapfile)
900 mapname = (templater.templatepath('map-cmdline.' + mapfile)
895 or templater.templatepath(mapfile))
901 or templater.templatepath(mapfile))
896 if mapname:
902 if mapname:
897 mapfile = mapname
903 mapfile = mapname
898
904
899 try:
905 try:
900 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
906 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
901 except SyntaxError, inst:
907 except SyntaxError, inst:
902 raise util.Abort(inst.args[0])
908 raise util.Abort(inst.args[0])
903 if tmpl:
909 if tmpl:
904 t.use_template(tmpl)
910 t.use_template(tmpl)
905 return t
911 return t
906
912
907 def finddate(ui, repo, date):
913 def finddate(ui, repo, date):
908 """Find the tipmost changeset that matches the given date spec"""
914 """Find the tipmost changeset that matches the given date spec"""
909
915
910 df = util.matchdate(date)
916 df = util.matchdate(date)
911 m = scmutil.matchall(repo)
917 m = scmutil.matchall(repo)
912 results = {}
918 results = {}
913
919
914 def prep(ctx, fns):
920 def prep(ctx, fns):
915 d = ctx.date()
921 d = ctx.date()
916 if df(d[0]):
922 if df(d[0]):
917 results[ctx.rev()] = d
923 results[ctx.rev()] = d
918
924
919 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
925 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
920 rev = ctx.rev()
926 rev = ctx.rev()
921 if rev in results:
927 if rev in results:
922 ui.status(_("Found revision %s from %s\n") %
928 ui.status(_("Found revision %s from %s\n") %
923 (rev, util.datestr(results[rev])))
929 (rev, util.datestr(results[rev])))
924 return str(rev)
930 return str(rev)
925
931
926 raise util.Abort(_("revision matching date not found"))
932 raise util.Abort(_("revision matching date not found"))
927
933
928 def walkchangerevs(repo, match, opts, prepare):
934 def walkchangerevs(repo, match, opts, prepare):
929 '''Iterate over files and the revs in which they changed.
935 '''Iterate over files and the revs in which they changed.
930
936
931 Callers most commonly need to iterate backwards over the history
937 Callers most commonly need to iterate backwards over the history
932 in which they are interested. Doing so has awful (quadratic-looking)
938 in which they are interested. Doing so has awful (quadratic-looking)
933 performance, so we use iterators in a "windowed" way.
939 performance, so we use iterators in a "windowed" way.
934
940
935 We walk a window of revisions in the desired order. Within the
941 We walk a window of revisions in the desired order. Within the
936 window, we first walk forwards to gather data, then in the desired
942 window, we first walk forwards to gather data, then in the desired
937 order (usually backwards) to display it.
943 order (usually backwards) to display it.
938
944
939 This function returns an iterator yielding contexts. Before
945 This function returns an iterator yielding contexts. Before
940 yielding each context, the iterator will first call the prepare
946 yielding each context, the iterator will first call the prepare
941 function on each context in the window in forward order.'''
947 function on each context in the window in forward order.'''
942
948
943 def increasing_windows(start, end, windowsize=8, sizelimit=512):
949 def increasing_windows(start, end, windowsize=8, sizelimit=512):
944 if start < end:
950 if start < end:
945 while start < end:
951 while start < end:
946 yield start, min(windowsize, end - start)
952 yield start, min(windowsize, end - start)
947 start += windowsize
953 start += windowsize
948 if windowsize < sizelimit:
954 if windowsize < sizelimit:
949 windowsize *= 2
955 windowsize *= 2
950 else:
956 else:
951 while start > end:
957 while start > end:
952 yield start, min(windowsize, start - end - 1)
958 yield start, min(windowsize, start - end - 1)
953 start -= windowsize
959 start -= windowsize
954 if windowsize < sizelimit:
960 if windowsize < sizelimit:
955 windowsize *= 2
961 windowsize *= 2
956
962
957 follow = opts.get('follow') or opts.get('follow_first')
963 follow = opts.get('follow') or opts.get('follow_first')
958
964
959 if not len(repo):
965 if not len(repo):
960 return []
966 return []
961
967
962 if follow:
968 if follow:
963 defrange = '%s:0' % repo['.'].rev()
969 defrange = '%s:0' % repo['.'].rev()
964 else:
970 else:
965 defrange = '-1:0'
971 defrange = '-1:0'
966 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
972 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
967 if not revs:
973 if not revs:
968 return []
974 return []
969 wanted = set()
975 wanted = set()
970 slowpath = match.anypats() or (match.files() and opts.get('removed'))
976 slowpath = match.anypats() or (match.files() and opts.get('removed'))
971 fncache = {}
977 fncache = {}
972 change = util.cachefunc(repo.changectx)
978 change = util.cachefunc(repo.changectx)
973
979
974 # First step is to fill wanted, the set of revisions that we want to yield.
980 # First step is to fill wanted, the set of revisions that we want to yield.
975 # When it does not induce extra cost, we also fill fncache for revisions in
981 # When it does not induce extra cost, we also fill fncache for revisions in
976 # wanted: a cache of filenames that were changed (ctx.files()) and that
982 # wanted: a cache of filenames that were changed (ctx.files()) and that
977 # match the file filtering conditions.
983 # match the file filtering conditions.
978
984
979 if not slowpath and not match.files():
985 if not slowpath and not match.files():
980 # No files, no patterns. Display all revs.
986 # No files, no patterns. Display all revs.
981 wanted = set(revs)
987 wanted = set(revs)
982 copies = []
988 copies = []
983
989
984 if not slowpath:
990 if not slowpath:
985 # We only have to read through the filelog to find wanted revisions
991 # We only have to read through the filelog to find wanted revisions
986
992
987 minrev, maxrev = min(revs), max(revs)
993 minrev, maxrev = min(revs), max(revs)
988 def filerevgen(filelog, last):
994 def filerevgen(filelog, last):
989 """
995 """
990 Only files, no patterns. Check the history of each file.
996 Only files, no patterns. Check the history of each file.
991
997
992 Examines filelog entries within minrev, maxrev linkrev range
998 Examines filelog entries within minrev, maxrev linkrev range
993 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
999 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
994 tuples in backwards order
1000 tuples in backwards order
995 """
1001 """
996 cl_count = len(repo)
1002 cl_count = len(repo)
997 revs = []
1003 revs = []
998 for j in xrange(0, last + 1):
1004 for j in xrange(0, last + 1):
999 linkrev = filelog.linkrev(j)
1005 linkrev = filelog.linkrev(j)
1000 if linkrev < minrev:
1006 if linkrev < minrev:
1001 continue
1007 continue
1002 # only yield rev for which we have the changelog, it can
1008 # only yield rev for which we have the changelog, it can
1003 # happen while doing "hg log" during a pull or commit
1009 # happen while doing "hg log" during a pull or commit
1004 if linkrev >= cl_count:
1010 if linkrev >= cl_count:
1005 break
1011 break
1006
1012
1007 parentlinkrevs = []
1013 parentlinkrevs = []
1008 for p in filelog.parentrevs(j):
1014 for p in filelog.parentrevs(j):
1009 if p != nullrev:
1015 if p != nullrev:
1010 parentlinkrevs.append(filelog.linkrev(p))
1016 parentlinkrevs.append(filelog.linkrev(p))
1011 n = filelog.node(j)
1017 n = filelog.node(j)
1012 revs.append((linkrev, parentlinkrevs,
1018 revs.append((linkrev, parentlinkrevs,
1013 follow and filelog.renamed(n)))
1019 follow and filelog.renamed(n)))
1014
1020
1015 return reversed(revs)
1021 return reversed(revs)
1016 def iterfiles():
1022 def iterfiles():
1017 for filename in match.files():
1023 for filename in match.files():
1018 yield filename, None
1024 yield filename, None
1019 for filename_node in copies:
1025 for filename_node in copies:
1020 yield filename_node
1026 yield filename_node
1021 for file_, node in iterfiles():
1027 for file_, node in iterfiles():
1022 filelog = repo.file(file_)
1028 filelog = repo.file(file_)
1023 if not len(filelog):
1029 if not len(filelog):
1024 if node is None:
1030 if node is None:
1025 # A zero count may be a directory or deleted file, so
1031 # A zero count may be a directory or deleted file, so
1026 # try to find matching entries on the slow path.
1032 # try to find matching entries on the slow path.
1027 if follow:
1033 if follow:
1028 raise util.Abort(
1034 raise util.Abort(
1029 _('cannot follow nonexistent file: "%s"') % file_)
1035 _('cannot follow nonexistent file: "%s"') % file_)
1030 slowpath = True
1036 slowpath = True
1031 break
1037 break
1032 else:
1038 else:
1033 continue
1039 continue
1034
1040
1035 if node is None:
1041 if node is None:
1036 last = len(filelog) - 1
1042 last = len(filelog) - 1
1037 else:
1043 else:
1038 last = filelog.rev(node)
1044 last = filelog.rev(node)
1039
1045
1040
1046
1041 # keep track of all ancestors of the file
1047 # keep track of all ancestors of the file
1042 ancestors = set([filelog.linkrev(last)])
1048 ancestors = set([filelog.linkrev(last)])
1043
1049
1044 # iterate from latest to oldest revision
1050 # iterate from latest to oldest revision
1045 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1051 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1046 if not follow:
1052 if not follow:
1047 if rev > maxrev:
1053 if rev > maxrev:
1048 continue
1054 continue
1049 else:
1055 else:
1050 # Note that last might not be the first interesting
1056 # Note that last might not be the first interesting
1051 # rev to us:
1057 # rev to us:
1052 # if the file has been changed after maxrev, we'll
1058 # if the file has been changed after maxrev, we'll
1053 # have linkrev(last) > maxrev, and we still need
1059 # have linkrev(last) > maxrev, and we still need
1054 # to explore the file graph
1060 # to explore the file graph
1055 if rev not in ancestors:
1061 if rev not in ancestors:
1056 continue
1062 continue
1057 # XXX insert 1327 fix here
1063 # XXX insert 1327 fix here
1058 if flparentlinkrevs:
1064 if flparentlinkrevs:
1059 ancestors.update(flparentlinkrevs)
1065 ancestors.update(flparentlinkrevs)
1060
1066
1061 fncache.setdefault(rev, []).append(file_)
1067 fncache.setdefault(rev, []).append(file_)
1062 wanted.add(rev)
1068 wanted.add(rev)
1063 if copied:
1069 if copied:
1064 copies.append(copied)
1070 copies.append(copied)
1065 if slowpath:
1071 if slowpath:
1066 # We have to read the changelog to match filenames against
1072 # We have to read the changelog to match filenames against
1067 # changed files
1073 # changed files
1068
1074
1069 if follow:
1075 if follow:
1070 raise util.Abort(_('can only follow copies/renames for explicit '
1076 raise util.Abort(_('can only follow copies/renames for explicit '
1071 'filenames'))
1077 'filenames'))
1072
1078
1073 # The slow path checks files modified in every changeset.
1079 # The slow path checks files modified in every changeset.
1074 for i in sorted(revs):
1080 for i in sorted(revs):
1075 ctx = change(i)
1081 ctx = change(i)
1076 matches = filter(match, ctx.files())
1082 matches = filter(match, ctx.files())
1077 if matches:
1083 if matches:
1078 fncache[i] = matches
1084 fncache[i] = matches
1079 wanted.add(i)
1085 wanted.add(i)
1080
1086
1081 class followfilter(object):
1087 class followfilter(object):
1082 def __init__(self, onlyfirst=False):
1088 def __init__(self, onlyfirst=False):
1083 self.startrev = nullrev
1089 self.startrev = nullrev
1084 self.roots = set()
1090 self.roots = set()
1085 self.onlyfirst = onlyfirst
1091 self.onlyfirst = onlyfirst
1086
1092
1087 def match(self, rev):
1093 def match(self, rev):
1088 def realparents(rev):
1094 def realparents(rev):
1089 if self.onlyfirst:
1095 if self.onlyfirst:
1090 return repo.changelog.parentrevs(rev)[0:1]
1096 return repo.changelog.parentrevs(rev)[0:1]
1091 else:
1097 else:
1092 return filter(lambda x: x != nullrev,
1098 return filter(lambda x: x != nullrev,
1093 repo.changelog.parentrevs(rev))
1099 repo.changelog.parentrevs(rev))
1094
1100
1095 if self.startrev == nullrev:
1101 if self.startrev == nullrev:
1096 self.startrev = rev
1102 self.startrev = rev
1097 return True
1103 return True
1098
1104
1099 if rev > self.startrev:
1105 if rev > self.startrev:
1100 # forward: all descendants
1106 # forward: all descendants
1101 if not self.roots:
1107 if not self.roots:
1102 self.roots.add(self.startrev)
1108 self.roots.add(self.startrev)
1103 for parent in realparents(rev):
1109 for parent in realparents(rev):
1104 if parent in self.roots:
1110 if parent in self.roots:
1105 self.roots.add(rev)
1111 self.roots.add(rev)
1106 return True
1112 return True
1107 else:
1113 else:
1108 # backwards: all parents
1114 # backwards: all parents
1109 if not self.roots:
1115 if not self.roots:
1110 self.roots.update(realparents(self.startrev))
1116 self.roots.update(realparents(self.startrev))
1111 if rev in self.roots:
1117 if rev in self.roots:
1112 self.roots.remove(rev)
1118 self.roots.remove(rev)
1113 self.roots.update(realparents(rev))
1119 self.roots.update(realparents(rev))
1114 return True
1120 return True
1115
1121
1116 return False
1122 return False
1117
1123
1118 # it might be worthwhile to do this in the iterator if the rev range
1124 # it might be worthwhile to do this in the iterator if the rev range
1119 # is descending and the prune args are all within that range
1125 # is descending and the prune args are all within that range
1120 for rev in opts.get('prune', ()):
1126 for rev in opts.get('prune', ()):
1121 rev = repo.changelog.rev(repo.lookup(rev))
1127 rev = repo.changelog.rev(repo.lookup(rev))
1122 ff = followfilter()
1128 ff = followfilter()
1123 stop = min(revs[0], revs[-1])
1129 stop = min(revs[0], revs[-1])
1124 for x in xrange(rev, stop - 1, -1):
1130 for x in xrange(rev, stop - 1, -1):
1125 if ff.match(x):
1131 if ff.match(x):
1126 wanted.discard(x)
1132 wanted.discard(x)
1127
1133
1128 # Now that wanted is correctly initialized, we can iterate over the
1134 # Now that wanted is correctly initialized, we can iterate over the
1129 # revision range, yielding only revisions in wanted.
1135 # revision range, yielding only revisions in wanted.
1130 def iterate():
1136 def iterate():
1131 if follow and not match.files():
1137 if follow and not match.files():
1132 ff = followfilter(onlyfirst=opts.get('follow_first'))
1138 ff = followfilter(onlyfirst=opts.get('follow_first'))
1133 def want(rev):
1139 def want(rev):
1134 return ff.match(rev) and rev in wanted
1140 return ff.match(rev) and rev in wanted
1135 else:
1141 else:
1136 def want(rev):
1142 def want(rev):
1137 return rev in wanted
1143 return rev in wanted
1138
1144
1139 for i, window in increasing_windows(0, len(revs)):
1145 for i, window in increasing_windows(0, len(revs)):
1140 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1146 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1141 for rev in sorted(nrevs):
1147 for rev in sorted(nrevs):
1142 fns = fncache.get(rev)
1148 fns = fncache.get(rev)
1143 ctx = change(rev)
1149 ctx = change(rev)
1144 if not fns:
1150 if not fns:
1145 def fns_generator():
1151 def fns_generator():
1146 for f in ctx.files():
1152 for f in ctx.files():
1147 if match(f):
1153 if match(f):
1148 yield f
1154 yield f
1149 fns = fns_generator()
1155 fns = fns_generator()
1150 prepare(ctx, fns)
1156 prepare(ctx, fns)
1151 for rev in nrevs:
1157 for rev in nrevs:
1152 yield change(rev)
1158 yield change(rev)
1153 return iterate()
1159 return iterate()
1154
1160
1155 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1161 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1156 join = lambda f: os.path.join(prefix, f)
1162 join = lambda f: os.path.join(prefix, f)
1157 bad = []
1163 bad = []
1158 oldbad = match.bad
1164 oldbad = match.bad
1159 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1165 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1160 names = []
1166 names = []
1161 wctx = repo[None]
1167 wctx = repo[None]
1162 cca = None
1168 cca = None
1163 abort, warn = scmutil.checkportabilityalert(ui)
1169 abort, warn = scmutil.checkportabilityalert(ui)
1164 if abort or warn:
1170 if abort or warn:
1165 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1171 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1166 for f in repo.walk(match):
1172 for f in repo.walk(match):
1167 exact = match.exact(f)
1173 exact = match.exact(f)
1168 if exact or f not in repo.dirstate:
1174 if exact or f not in repo.dirstate:
1169 if cca:
1175 if cca:
1170 cca(f)
1176 cca(f)
1171 names.append(f)
1177 names.append(f)
1172 if ui.verbose or not exact:
1178 if ui.verbose or not exact:
1173 ui.status(_('adding %s\n') % match.rel(join(f)))
1179 ui.status(_('adding %s\n') % match.rel(join(f)))
1174
1180
1175 if listsubrepos:
1181 if listsubrepos:
1176 for subpath in wctx.substate:
1182 for subpath in wctx.substate:
1177 sub = wctx.sub(subpath)
1183 sub = wctx.sub(subpath)
1178 try:
1184 try:
1179 submatch = matchmod.narrowmatcher(subpath, match)
1185 submatch = matchmod.narrowmatcher(subpath, match)
1180 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1186 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1181 except error.LookupError:
1187 except error.LookupError:
1182 ui.status(_("skipping missing subrepository: %s\n")
1188 ui.status(_("skipping missing subrepository: %s\n")
1183 % join(subpath))
1189 % join(subpath))
1184
1190
1185 if not dryrun:
1191 if not dryrun:
1186 rejected = wctx.add(names, prefix)
1192 rejected = wctx.add(names, prefix)
1187 bad.extend(f for f in rejected if f in match.files())
1193 bad.extend(f for f in rejected if f in match.files())
1188 return bad
1194 return bad
1189
1195
1190 def duplicatecopies(repo, rev, p1, p2):
1196 def duplicatecopies(repo, rev, p1, p2):
1191 "Reproduce copies found in the source revision in the dirstate for grafts"
1197 "Reproduce copies found in the source revision in the dirstate for grafts"
1192 # Here we simulate the copies and renames in the source changeset
1198 # Here we simulate the copies and renames in the source changeset
1193 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
1199 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
1194 m1 = repo[rev].manifest()
1200 m1 = repo[rev].manifest()
1195 m2 = repo[p1].manifest()
1201 m2 = repo[p1].manifest()
1196 for k, v in cop.iteritems():
1202 for k, v in cop.iteritems():
1197 if k in m1:
1203 if k in m1:
1198 if v in m1 or v in m2:
1204 if v in m1 or v in m2:
1199 repo.dirstate.copy(v, k)
1205 repo.dirstate.copy(v, k)
1200 if v in m2 and v not in m1 and k in m2:
1206 if v in m2 and v not in m1 and k in m2:
1201 repo.dirstate.remove(v)
1207 repo.dirstate.remove(v)
1202
1208
1203 def commit(ui, repo, commitfunc, pats, opts):
1209 def commit(ui, repo, commitfunc, pats, opts):
1204 '''commit the specified files or all outstanding changes'''
1210 '''commit the specified files or all outstanding changes'''
1205 date = opts.get('date')
1211 date = opts.get('date')
1206 if date:
1212 if date:
1207 opts['date'] = util.parsedate(date)
1213 opts['date'] = util.parsedate(date)
1208 message = logmessage(ui, opts)
1214 message = logmessage(ui, opts)
1209
1215
1210 # extract addremove carefully -- this function can be called from a command
1216 # extract addremove carefully -- this function can be called from a command
1211 # that doesn't support addremove
1217 # that doesn't support addremove
1212 if opts.get('addremove'):
1218 if opts.get('addremove'):
1213 scmutil.addremove(repo, pats, opts)
1219 scmutil.addremove(repo, pats, opts)
1214
1220
1215 return commitfunc(ui, repo, message,
1221 return commitfunc(ui, repo, message,
1216 scmutil.match(repo[None], pats, opts), opts)
1222 scmutil.match(repo[None], pats, opts), opts)
1217
1223
1218 def commiteditor(repo, ctx, subs):
1224 def commiteditor(repo, ctx, subs):
1219 if ctx.description():
1225 if ctx.description():
1220 return ctx.description()
1226 return ctx.description()
1221 return commitforceeditor(repo, ctx, subs)
1227 return commitforceeditor(repo, ctx, subs)
1222
1228
1223 def commitforceeditor(repo, ctx, subs):
1229 def commitforceeditor(repo, ctx, subs):
1224 edittext = []
1230 edittext = []
1225 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1231 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1226 if ctx.description():
1232 if ctx.description():
1227 edittext.append(ctx.description())
1233 edittext.append(ctx.description())
1228 edittext.append("")
1234 edittext.append("")
1229 edittext.append("") # Empty line between message and comments.
1235 edittext.append("") # Empty line between message and comments.
1230 edittext.append(_("HG: Enter commit message."
1236 edittext.append(_("HG: Enter commit message."
1231 " Lines beginning with 'HG:' are removed."))
1237 " Lines beginning with 'HG:' are removed."))
1232 edittext.append(_("HG: Leave message empty to abort commit."))
1238 edittext.append(_("HG: Leave message empty to abort commit."))
1233 edittext.append("HG: --")
1239 edittext.append("HG: --")
1234 edittext.append(_("HG: user: %s") % ctx.user())
1240 edittext.append(_("HG: user: %s") % ctx.user())
1235 if ctx.p2():
1241 if ctx.p2():
1236 edittext.append(_("HG: branch merge"))
1242 edittext.append(_("HG: branch merge"))
1237 if ctx.branch():
1243 if ctx.branch():
1238 edittext.append(_("HG: branch '%s'") % ctx.branch())
1244 edittext.append(_("HG: branch '%s'") % ctx.branch())
1239 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1245 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1240 edittext.extend([_("HG: added %s") % f for f in added])
1246 edittext.extend([_("HG: added %s") % f for f in added])
1241 edittext.extend([_("HG: changed %s") % f for f in modified])
1247 edittext.extend([_("HG: changed %s") % f for f in modified])
1242 edittext.extend([_("HG: removed %s") % f for f in removed])
1248 edittext.extend([_("HG: removed %s") % f for f in removed])
1243 if not added and not modified and not removed:
1249 if not added and not modified and not removed:
1244 edittext.append(_("HG: no files changed"))
1250 edittext.append(_("HG: no files changed"))
1245 edittext.append("")
1251 edittext.append("")
1246 # run editor in the repository root
1252 # run editor in the repository root
1247 olddir = os.getcwd()
1253 olddir = os.getcwd()
1248 os.chdir(repo.root)
1254 os.chdir(repo.root)
1249 text = repo.ui.edit("\n".join(edittext), ctx.user())
1255 text = repo.ui.edit("\n".join(edittext), ctx.user())
1250 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1256 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1251 os.chdir(olddir)
1257 os.chdir(olddir)
1252
1258
1253 if not text.strip():
1259 if not text.strip():
1254 raise util.Abort(_("empty commit message"))
1260 raise util.Abort(_("empty commit message"))
1255
1261
1256 return text
1262 return text
1257
1263
1258 def command(table):
1264 def command(table):
1259 '''returns a function object bound to table which can be used as
1265 '''returns a function object bound to table which can be used as
1260 a decorator for populating table as a command table'''
1266 a decorator for populating table as a command table'''
1261
1267
1262 def cmd(name, options, synopsis=None):
1268 def cmd(name, options, synopsis=None):
1263 def decorator(func):
1269 def decorator(func):
1264 if synopsis:
1270 if synopsis:
1265 table[name] = func, options[:], synopsis
1271 table[name] = func, options[:], synopsis
1266 else:
1272 else:
1267 table[name] = func, options[:]
1273 table[name] = func, options[:]
1268 return func
1274 return func
1269 return decorator
1275 return decorator
1270
1276
1271 return cmd
1277 return cmd
General Comments 0
You need to be logged in to leave comments. Login now