##// END OF EJS Templates
diff: when diffing a revision with a deleted subrepo, maintain the node context (issue3153)
Alistair Bell -
r15698:43e068c1 stable
parent child Browse files
Show More
@@ -1,1277 +1,1278 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 tempnode2 = node2
591 try:
592 try:
592 if node2 is not None:
593 if node2 is not None:
593 node2 = ctx2.substate[subpath][1]
594 tempnode2 = ctx2.substate[subpath][1]
594 except KeyError:
595 except KeyError:
595 # A subrepo that existed in node1 was deleted between node1 and
596 # A subrepo that existed in node1 was deleted between node1 and
596 # node2 (inclusive). Thus, ctx2's substate won't contain that
597 # node2 (inclusive). Thus, ctx2's substate won't contain that
597 # subpath. The best we can do is to ignore it.
598 # subpath. The best we can do is to ignore it.
598 node2 = None
599 tempnode2 = None
599 submatch = matchmod.narrowmatcher(subpath, match)
600 submatch = matchmod.narrowmatcher(subpath, match)
600 sub.diff(diffopts, node2, submatch, changes=changes,
601 sub.diff(diffopts, tempnode2, submatch, changes=changes,
601 stat=stat, fp=fp, prefix=prefix)
602 stat=stat, fp=fp, prefix=prefix)
602
603
603 class changeset_printer(object):
604 class changeset_printer(object):
604 '''show changeset information when templating not requested.'''
605 '''show changeset information when templating not requested.'''
605
606
606 def __init__(self, ui, repo, patch, diffopts, buffered):
607 def __init__(self, ui, repo, patch, diffopts, buffered):
607 self.ui = ui
608 self.ui = ui
608 self.repo = repo
609 self.repo = repo
609 self.buffered = buffered
610 self.buffered = buffered
610 self.patch = patch
611 self.patch = patch
611 self.diffopts = diffopts
612 self.diffopts = diffopts
612 self.header = {}
613 self.header = {}
613 self.hunk = {}
614 self.hunk = {}
614 self.lastheader = None
615 self.lastheader = None
615 self.footer = None
616 self.footer = None
616
617
617 def flush(self, rev):
618 def flush(self, rev):
618 if rev in self.header:
619 if rev in self.header:
619 h = self.header[rev]
620 h = self.header[rev]
620 if h != self.lastheader:
621 if h != self.lastheader:
621 self.lastheader = h
622 self.lastheader = h
622 self.ui.write(h)
623 self.ui.write(h)
623 del self.header[rev]
624 del self.header[rev]
624 if rev in self.hunk:
625 if rev in self.hunk:
625 self.ui.write(self.hunk[rev])
626 self.ui.write(self.hunk[rev])
626 del self.hunk[rev]
627 del self.hunk[rev]
627 return 1
628 return 1
628 return 0
629 return 0
629
630
630 def close(self):
631 def close(self):
631 if self.footer:
632 if self.footer:
632 self.ui.write(self.footer)
633 self.ui.write(self.footer)
633
634
634 def show(self, ctx, copies=None, matchfn=None, **props):
635 def show(self, ctx, copies=None, matchfn=None, **props):
635 if self.buffered:
636 if self.buffered:
636 self.ui.pushbuffer()
637 self.ui.pushbuffer()
637 self._show(ctx, copies, matchfn, props)
638 self._show(ctx, copies, matchfn, props)
638 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
639 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
639 else:
640 else:
640 self._show(ctx, copies, matchfn, props)
641 self._show(ctx, copies, matchfn, props)
641
642
642 def _show(self, ctx, copies, matchfn, props):
643 def _show(self, ctx, copies, matchfn, props):
643 '''show a single changeset or file revision'''
644 '''show a single changeset or file revision'''
644 changenode = ctx.node()
645 changenode = ctx.node()
645 rev = ctx.rev()
646 rev = ctx.rev()
646
647
647 if self.ui.quiet:
648 if self.ui.quiet:
648 self.ui.write("%d:%s\n" % (rev, short(changenode)),
649 self.ui.write("%d:%s\n" % (rev, short(changenode)),
649 label='log.node')
650 label='log.node')
650 return
651 return
651
652
652 log = self.repo.changelog
653 log = self.repo.changelog
653 date = util.datestr(ctx.date())
654 date = util.datestr(ctx.date())
654
655
655 hexfunc = self.ui.debugflag and hex or short
656 hexfunc = self.ui.debugflag and hex or short
656
657
657 parents = [(p, hexfunc(log.node(p)))
658 parents = [(p, hexfunc(log.node(p)))
658 for p in self._meaningful_parentrevs(log, rev)]
659 for p in self._meaningful_parentrevs(log, rev)]
659
660
660 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
661 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
661 label='log.changeset')
662 label='log.changeset')
662
663
663 branch = ctx.branch()
664 branch = ctx.branch()
664 # don't show the default branch name
665 # don't show the default branch name
665 if branch != 'default':
666 if branch != 'default':
666 self.ui.write(_("branch: %s\n") % branch,
667 self.ui.write(_("branch: %s\n") % branch,
667 label='log.branch')
668 label='log.branch')
668 for bookmark in self.repo.nodebookmarks(changenode):
669 for bookmark in self.repo.nodebookmarks(changenode):
669 self.ui.write(_("bookmark: %s\n") % bookmark,
670 self.ui.write(_("bookmark: %s\n") % bookmark,
670 label='log.bookmark')
671 label='log.bookmark')
671 for tag in self.repo.nodetags(changenode):
672 for tag in self.repo.nodetags(changenode):
672 self.ui.write(_("tag: %s\n") % tag,
673 self.ui.write(_("tag: %s\n") % tag,
673 label='log.tag')
674 label='log.tag')
674 for parent in parents:
675 for parent in parents:
675 self.ui.write(_("parent: %d:%s\n") % parent,
676 self.ui.write(_("parent: %d:%s\n") % parent,
676 label='log.parent')
677 label='log.parent')
677
678
678 if self.ui.debugflag:
679 if self.ui.debugflag:
679 mnode = ctx.manifestnode()
680 mnode = ctx.manifestnode()
680 self.ui.write(_("manifest: %d:%s\n") %
681 self.ui.write(_("manifest: %d:%s\n") %
681 (self.repo.manifest.rev(mnode), hex(mnode)),
682 (self.repo.manifest.rev(mnode), hex(mnode)),
682 label='ui.debug log.manifest')
683 label='ui.debug log.manifest')
683 self.ui.write(_("user: %s\n") % ctx.user(),
684 self.ui.write(_("user: %s\n") % ctx.user(),
684 label='log.user')
685 label='log.user')
685 self.ui.write(_("date: %s\n") % date,
686 self.ui.write(_("date: %s\n") % date,
686 label='log.date')
687 label='log.date')
687
688
688 if self.ui.debugflag:
689 if self.ui.debugflag:
689 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
690 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
690 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
691 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
691 files):
692 files):
692 if value:
693 if value:
693 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
694 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
694 label='ui.debug log.files')
695 label='ui.debug log.files')
695 elif ctx.files() and self.ui.verbose:
696 elif ctx.files() and self.ui.verbose:
696 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
697 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
697 label='ui.note log.files')
698 label='ui.note log.files')
698 if copies and self.ui.verbose:
699 if copies and self.ui.verbose:
699 copies = ['%s (%s)' % c for c in copies]
700 copies = ['%s (%s)' % c for c in copies]
700 self.ui.write(_("copies: %s\n") % ' '.join(copies),
701 self.ui.write(_("copies: %s\n") % ' '.join(copies),
701 label='ui.note log.copies')
702 label='ui.note log.copies')
702
703
703 extra = ctx.extra()
704 extra = ctx.extra()
704 if extra and self.ui.debugflag:
705 if extra and self.ui.debugflag:
705 for key, value in sorted(extra.items()):
706 for key, value in sorted(extra.items()):
706 self.ui.write(_("extra: %s=%s\n")
707 self.ui.write(_("extra: %s=%s\n")
707 % (key, value.encode('string_escape')),
708 % (key, value.encode('string_escape')),
708 label='ui.debug log.extra')
709 label='ui.debug log.extra')
709
710
710 description = ctx.description().strip()
711 description = ctx.description().strip()
711 if description:
712 if description:
712 if self.ui.verbose:
713 if self.ui.verbose:
713 self.ui.write(_("description:\n"),
714 self.ui.write(_("description:\n"),
714 label='ui.note log.description')
715 label='ui.note log.description')
715 self.ui.write(description,
716 self.ui.write(description,
716 label='ui.note log.description')
717 label='ui.note log.description')
717 self.ui.write("\n\n")
718 self.ui.write("\n\n")
718 else:
719 else:
719 self.ui.write(_("summary: %s\n") %
720 self.ui.write(_("summary: %s\n") %
720 description.splitlines()[0],
721 description.splitlines()[0],
721 label='log.summary')
722 label='log.summary')
722 self.ui.write("\n")
723 self.ui.write("\n")
723
724
724 self.showpatch(changenode, matchfn)
725 self.showpatch(changenode, matchfn)
725
726
726 def showpatch(self, node, matchfn):
727 def showpatch(self, node, matchfn):
727 if not matchfn:
728 if not matchfn:
728 matchfn = self.patch
729 matchfn = self.patch
729 if matchfn:
730 if matchfn:
730 stat = self.diffopts.get('stat')
731 stat = self.diffopts.get('stat')
731 diff = self.diffopts.get('patch')
732 diff = self.diffopts.get('patch')
732 diffopts = patch.diffopts(self.ui, self.diffopts)
733 diffopts = patch.diffopts(self.ui, self.diffopts)
733 prev = self.repo.changelog.parents(node)[0]
734 prev = self.repo.changelog.parents(node)[0]
734 if stat:
735 if stat:
735 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
736 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
736 match=matchfn, stat=True)
737 match=matchfn, stat=True)
737 if diff:
738 if diff:
738 if stat:
739 if stat:
739 self.ui.write("\n")
740 self.ui.write("\n")
740 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
741 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
741 match=matchfn, stat=False)
742 match=matchfn, stat=False)
742 self.ui.write("\n")
743 self.ui.write("\n")
743
744
744 def _meaningful_parentrevs(self, log, rev):
745 def _meaningful_parentrevs(self, log, rev):
745 """Return list of meaningful (or all if debug) parentrevs for rev.
746 """Return list of meaningful (or all if debug) parentrevs for rev.
746
747
747 For merges (two non-nullrev revisions) both parents are meaningful.
748 For merges (two non-nullrev revisions) both parents are meaningful.
748 Otherwise the first parent revision is considered meaningful if it
749 Otherwise the first parent revision is considered meaningful if it
749 is not the preceding revision.
750 is not the preceding revision.
750 """
751 """
751 parents = log.parentrevs(rev)
752 parents = log.parentrevs(rev)
752 if not self.ui.debugflag and parents[1] == nullrev:
753 if not self.ui.debugflag and parents[1] == nullrev:
753 if parents[0] >= rev - 1:
754 if parents[0] >= rev - 1:
754 parents = []
755 parents = []
755 else:
756 else:
756 parents = [parents[0]]
757 parents = [parents[0]]
757 return parents
758 return parents
758
759
759
760
760 class changeset_templater(changeset_printer):
761 class changeset_templater(changeset_printer):
761 '''format changeset information.'''
762 '''format changeset information.'''
762
763
763 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
764 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
764 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
765 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
765 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
766 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
766 defaulttempl = {
767 defaulttempl = {
767 'parent': '{rev}:{node|formatnode} ',
768 'parent': '{rev}:{node|formatnode} ',
768 'manifest': '{rev}:{node|formatnode}',
769 'manifest': '{rev}:{node|formatnode}',
769 'file_copy': '{name} ({source})',
770 'file_copy': '{name} ({source})',
770 'extra': '{key}={value|stringescape}'
771 'extra': '{key}={value|stringescape}'
771 }
772 }
772 # filecopy is preserved for compatibility reasons
773 # filecopy is preserved for compatibility reasons
773 defaulttempl['filecopy'] = defaulttempl['file_copy']
774 defaulttempl['filecopy'] = defaulttempl['file_copy']
774 self.t = templater.templater(mapfile, {'formatnode': formatnode},
775 self.t = templater.templater(mapfile, {'formatnode': formatnode},
775 cache=defaulttempl)
776 cache=defaulttempl)
776 self.cache = {}
777 self.cache = {}
777
778
778 def use_template(self, t):
779 def use_template(self, t):
779 '''set template string to use'''
780 '''set template string to use'''
780 self.t.cache['changeset'] = t
781 self.t.cache['changeset'] = t
781
782
782 def _meaningful_parentrevs(self, ctx):
783 def _meaningful_parentrevs(self, ctx):
783 """Return list of meaningful (or all if debug) parentrevs for rev.
784 """Return list of meaningful (or all if debug) parentrevs for rev.
784 """
785 """
785 parents = ctx.parents()
786 parents = ctx.parents()
786 if len(parents) > 1:
787 if len(parents) > 1:
787 return parents
788 return parents
788 if self.ui.debugflag:
789 if self.ui.debugflag:
789 return [parents[0], self.repo['null']]
790 return [parents[0], self.repo['null']]
790 if parents[0].rev() >= ctx.rev() - 1:
791 if parents[0].rev() >= ctx.rev() - 1:
791 return []
792 return []
792 return parents
793 return parents
793
794
794 def _show(self, ctx, copies, matchfn, props):
795 def _show(self, ctx, copies, matchfn, props):
795 '''show a single changeset or file revision'''
796 '''show a single changeset or file revision'''
796
797
797 showlist = templatekw.showlist
798 showlist = templatekw.showlist
798
799
799 # showparents() behaviour depends on ui trace level which
800 # showparents() behaviour depends on ui trace level which
800 # causes unexpected behaviours at templating level and makes
801 # causes unexpected behaviours at templating level and makes
801 # it harder to extract it in a standalone function. Its
802 # it harder to extract it in a standalone function. Its
802 # behaviour cannot be changed so leave it here for now.
803 # behaviour cannot be changed so leave it here for now.
803 def showparents(**args):
804 def showparents(**args):
804 ctx = args['ctx']
805 ctx = args['ctx']
805 parents = [[('rev', p.rev()), ('node', p.hex())]
806 parents = [[('rev', p.rev()), ('node', p.hex())]
806 for p in self._meaningful_parentrevs(ctx)]
807 for p in self._meaningful_parentrevs(ctx)]
807 return showlist('parent', parents, **args)
808 return showlist('parent', parents, **args)
808
809
809 props = props.copy()
810 props = props.copy()
810 props.update(templatekw.keywords)
811 props.update(templatekw.keywords)
811 props['parents'] = showparents
812 props['parents'] = showparents
812 props['templ'] = self.t
813 props['templ'] = self.t
813 props['ctx'] = ctx
814 props['ctx'] = ctx
814 props['repo'] = self.repo
815 props['repo'] = self.repo
815 props['revcache'] = {'copies': copies}
816 props['revcache'] = {'copies': copies}
816 props['cache'] = self.cache
817 props['cache'] = self.cache
817
818
818 # find correct templates for current mode
819 # find correct templates for current mode
819
820
820 tmplmodes = [
821 tmplmodes = [
821 (True, None),
822 (True, None),
822 (self.ui.verbose, 'verbose'),
823 (self.ui.verbose, 'verbose'),
823 (self.ui.quiet, 'quiet'),
824 (self.ui.quiet, 'quiet'),
824 (self.ui.debugflag, 'debug'),
825 (self.ui.debugflag, 'debug'),
825 ]
826 ]
826
827
827 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
828 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
828 for mode, postfix in tmplmodes:
829 for mode, postfix in tmplmodes:
829 for type in types:
830 for type in types:
830 cur = postfix and ('%s_%s' % (type, postfix)) or type
831 cur = postfix and ('%s_%s' % (type, postfix)) or type
831 if mode and cur in self.t:
832 if mode and cur in self.t:
832 types[type] = cur
833 types[type] = cur
833
834
834 try:
835 try:
835
836
836 # write header
837 # write header
837 if types['header']:
838 if types['header']:
838 h = templater.stringify(self.t(types['header'], **props))
839 h = templater.stringify(self.t(types['header'], **props))
839 if self.buffered:
840 if self.buffered:
840 self.header[ctx.rev()] = h
841 self.header[ctx.rev()] = h
841 else:
842 else:
842 if self.lastheader != h:
843 if self.lastheader != h:
843 self.lastheader = h
844 self.lastheader = h
844 self.ui.write(h)
845 self.ui.write(h)
845
846
846 # write changeset metadata, then patch if requested
847 # write changeset metadata, then patch if requested
847 key = types['changeset']
848 key = types['changeset']
848 self.ui.write(templater.stringify(self.t(key, **props)))
849 self.ui.write(templater.stringify(self.t(key, **props)))
849 self.showpatch(ctx.node(), matchfn)
850 self.showpatch(ctx.node(), matchfn)
850
851
851 if types['footer']:
852 if types['footer']:
852 if not self.footer:
853 if not self.footer:
853 self.footer = templater.stringify(self.t(types['footer'],
854 self.footer = templater.stringify(self.t(types['footer'],
854 **props))
855 **props))
855
856
856 except KeyError, inst:
857 except KeyError, inst:
857 msg = _("%s: no key named '%s'")
858 msg = _("%s: no key named '%s'")
858 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
859 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
859 except SyntaxError, inst:
860 except SyntaxError, inst:
860 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
861 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
861
862
862 def show_changeset(ui, repo, opts, buffered=False):
863 def show_changeset(ui, repo, opts, buffered=False):
863 """show one changeset using template or regular display.
864 """show one changeset using template or regular display.
864
865
865 Display format will be the first non-empty hit of:
866 Display format will be the first non-empty hit of:
866 1. option 'template'
867 1. option 'template'
867 2. option 'style'
868 2. option 'style'
868 3. [ui] setting 'logtemplate'
869 3. [ui] setting 'logtemplate'
869 4. [ui] setting 'style'
870 4. [ui] setting 'style'
870 If all of these values are either the unset or the empty string,
871 If all of these values are either the unset or the empty string,
871 regular display via changeset_printer() is done.
872 regular display via changeset_printer() is done.
872 """
873 """
873 # options
874 # options
874 patch = False
875 patch = False
875 if opts.get('patch') or opts.get('stat'):
876 if opts.get('patch') or opts.get('stat'):
876 patch = scmutil.matchall(repo)
877 patch = scmutil.matchall(repo)
877
878
878 tmpl = opts.get('template')
879 tmpl = opts.get('template')
879 style = None
880 style = None
880 if tmpl:
881 if tmpl:
881 tmpl = templater.parsestring(tmpl, quoted=False)
882 tmpl = templater.parsestring(tmpl, quoted=False)
882 else:
883 else:
883 style = opts.get('style')
884 style = opts.get('style')
884
885
885 # ui settings
886 # ui settings
886 if not (tmpl or style):
887 if not (tmpl or style):
887 tmpl = ui.config('ui', 'logtemplate')
888 tmpl = ui.config('ui', 'logtemplate')
888 if tmpl:
889 if tmpl:
889 tmpl = templater.parsestring(tmpl)
890 tmpl = templater.parsestring(tmpl)
890 else:
891 else:
891 style = util.expandpath(ui.config('ui', 'style', ''))
892 style = util.expandpath(ui.config('ui', 'style', ''))
892
893
893 if not (tmpl or style):
894 if not (tmpl or style):
894 return changeset_printer(ui, repo, patch, opts, buffered)
895 return changeset_printer(ui, repo, patch, opts, buffered)
895
896
896 mapfile = None
897 mapfile = None
897 if style and not tmpl:
898 if style and not tmpl:
898 mapfile = style
899 mapfile = style
899 if not os.path.split(mapfile)[0]:
900 if not os.path.split(mapfile)[0]:
900 mapname = (templater.templatepath('map-cmdline.' + mapfile)
901 mapname = (templater.templatepath('map-cmdline.' + mapfile)
901 or templater.templatepath(mapfile))
902 or templater.templatepath(mapfile))
902 if mapname:
903 if mapname:
903 mapfile = mapname
904 mapfile = mapname
904
905
905 try:
906 try:
906 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
907 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
907 except SyntaxError, inst:
908 except SyntaxError, inst:
908 raise util.Abort(inst.args[0])
909 raise util.Abort(inst.args[0])
909 if tmpl:
910 if tmpl:
910 t.use_template(tmpl)
911 t.use_template(tmpl)
911 return t
912 return t
912
913
913 def finddate(ui, repo, date):
914 def finddate(ui, repo, date):
914 """Find the tipmost changeset that matches the given date spec"""
915 """Find the tipmost changeset that matches the given date spec"""
915
916
916 df = util.matchdate(date)
917 df = util.matchdate(date)
917 m = scmutil.matchall(repo)
918 m = scmutil.matchall(repo)
918 results = {}
919 results = {}
919
920
920 def prep(ctx, fns):
921 def prep(ctx, fns):
921 d = ctx.date()
922 d = ctx.date()
922 if df(d[0]):
923 if df(d[0]):
923 results[ctx.rev()] = d
924 results[ctx.rev()] = d
924
925
925 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
926 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
926 rev = ctx.rev()
927 rev = ctx.rev()
927 if rev in results:
928 if rev in results:
928 ui.status(_("Found revision %s from %s\n") %
929 ui.status(_("Found revision %s from %s\n") %
929 (rev, util.datestr(results[rev])))
930 (rev, util.datestr(results[rev])))
930 return str(rev)
931 return str(rev)
931
932
932 raise util.Abort(_("revision matching date not found"))
933 raise util.Abort(_("revision matching date not found"))
933
934
934 def walkchangerevs(repo, match, opts, prepare):
935 def walkchangerevs(repo, match, opts, prepare):
935 '''Iterate over files and the revs in which they changed.
936 '''Iterate over files and the revs in which they changed.
936
937
937 Callers most commonly need to iterate backwards over the history
938 Callers most commonly need to iterate backwards over the history
938 in which they are interested. Doing so has awful (quadratic-looking)
939 in which they are interested. Doing so has awful (quadratic-looking)
939 performance, so we use iterators in a "windowed" way.
940 performance, so we use iterators in a "windowed" way.
940
941
941 We walk a window of revisions in the desired order. Within the
942 We walk a window of revisions in the desired order. Within the
942 window, we first walk forwards to gather data, then in the desired
943 window, we first walk forwards to gather data, then in the desired
943 order (usually backwards) to display it.
944 order (usually backwards) to display it.
944
945
945 This function returns an iterator yielding contexts. Before
946 This function returns an iterator yielding contexts. Before
946 yielding each context, the iterator will first call the prepare
947 yielding each context, the iterator will first call the prepare
947 function on each context in the window in forward order.'''
948 function on each context in the window in forward order.'''
948
949
949 def increasing_windows(start, end, windowsize=8, sizelimit=512):
950 def increasing_windows(start, end, windowsize=8, sizelimit=512):
950 if start < end:
951 if start < end:
951 while start < end:
952 while start < end:
952 yield start, min(windowsize, end - start)
953 yield start, min(windowsize, end - start)
953 start += windowsize
954 start += windowsize
954 if windowsize < sizelimit:
955 if windowsize < sizelimit:
955 windowsize *= 2
956 windowsize *= 2
956 else:
957 else:
957 while start > end:
958 while start > end:
958 yield start, min(windowsize, start - end - 1)
959 yield start, min(windowsize, start - end - 1)
959 start -= windowsize
960 start -= windowsize
960 if windowsize < sizelimit:
961 if windowsize < sizelimit:
961 windowsize *= 2
962 windowsize *= 2
962
963
963 follow = opts.get('follow') or opts.get('follow_first')
964 follow = opts.get('follow') or opts.get('follow_first')
964
965
965 if not len(repo):
966 if not len(repo):
966 return []
967 return []
967
968
968 if follow:
969 if follow:
969 defrange = '%s:0' % repo['.'].rev()
970 defrange = '%s:0' % repo['.'].rev()
970 else:
971 else:
971 defrange = '-1:0'
972 defrange = '-1:0'
972 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
973 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
973 if not revs:
974 if not revs:
974 return []
975 return []
975 wanted = set()
976 wanted = set()
976 slowpath = match.anypats() or (match.files() and opts.get('removed'))
977 slowpath = match.anypats() or (match.files() and opts.get('removed'))
977 fncache = {}
978 fncache = {}
978 change = util.cachefunc(repo.changectx)
979 change = util.cachefunc(repo.changectx)
979
980
980 # First step is to fill wanted, the set of revisions that we want to yield.
981 # First step is to fill wanted, the set of revisions that we want to yield.
981 # When it does not induce extra cost, we also fill fncache for revisions in
982 # When it does not induce extra cost, we also fill fncache for revisions in
982 # wanted: a cache of filenames that were changed (ctx.files()) and that
983 # wanted: a cache of filenames that were changed (ctx.files()) and that
983 # match the file filtering conditions.
984 # match the file filtering conditions.
984
985
985 if not slowpath and not match.files():
986 if not slowpath and not match.files():
986 # No files, no patterns. Display all revs.
987 # No files, no patterns. Display all revs.
987 wanted = set(revs)
988 wanted = set(revs)
988 copies = []
989 copies = []
989
990
990 if not slowpath:
991 if not slowpath:
991 # We only have to read through the filelog to find wanted revisions
992 # We only have to read through the filelog to find wanted revisions
992
993
993 minrev, maxrev = min(revs), max(revs)
994 minrev, maxrev = min(revs), max(revs)
994 def filerevgen(filelog, last):
995 def filerevgen(filelog, last):
995 """
996 """
996 Only files, no patterns. Check the history of each file.
997 Only files, no patterns. Check the history of each file.
997
998
998 Examines filelog entries within minrev, maxrev linkrev range
999 Examines filelog entries within minrev, maxrev linkrev range
999 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1000 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1000 tuples in backwards order
1001 tuples in backwards order
1001 """
1002 """
1002 cl_count = len(repo)
1003 cl_count = len(repo)
1003 revs = []
1004 revs = []
1004 for j in xrange(0, last + 1):
1005 for j in xrange(0, last + 1):
1005 linkrev = filelog.linkrev(j)
1006 linkrev = filelog.linkrev(j)
1006 if linkrev < minrev:
1007 if linkrev < minrev:
1007 continue
1008 continue
1008 # only yield rev for which we have the changelog, it can
1009 # only yield rev for which we have the changelog, it can
1009 # happen while doing "hg log" during a pull or commit
1010 # happen while doing "hg log" during a pull or commit
1010 if linkrev >= cl_count:
1011 if linkrev >= cl_count:
1011 break
1012 break
1012
1013
1013 parentlinkrevs = []
1014 parentlinkrevs = []
1014 for p in filelog.parentrevs(j):
1015 for p in filelog.parentrevs(j):
1015 if p != nullrev:
1016 if p != nullrev:
1016 parentlinkrevs.append(filelog.linkrev(p))
1017 parentlinkrevs.append(filelog.linkrev(p))
1017 n = filelog.node(j)
1018 n = filelog.node(j)
1018 revs.append((linkrev, parentlinkrevs,
1019 revs.append((linkrev, parentlinkrevs,
1019 follow and filelog.renamed(n)))
1020 follow and filelog.renamed(n)))
1020
1021
1021 return reversed(revs)
1022 return reversed(revs)
1022 def iterfiles():
1023 def iterfiles():
1023 for filename in match.files():
1024 for filename in match.files():
1024 yield filename, None
1025 yield filename, None
1025 for filename_node in copies:
1026 for filename_node in copies:
1026 yield filename_node
1027 yield filename_node
1027 for file_, node in iterfiles():
1028 for file_, node in iterfiles():
1028 filelog = repo.file(file_)
1029 filelog = repo.file(file_)
1029 if not len(filelog):
1030 if not len(filelog):
1030 if node is None:
1031 if node is None:
1031 # A zero count may be a directory or deleted file, so
1032 # A zero count may be a directory or deleted file, so
1032 # try to find matching entries on the slow path.
1033 # try to find matching entries on the slow path.
1033 if follow:
1034 if follow:
1034 raise util.Abort(
1035 raise util.Abort(
1035 _('cannot follow nonexistent file: "%s"') % file_)
1036 _('cannot follow nonexistent file: "%s"') % file_)
1036 slowpath = True
1037 slowpath = True
1037 break
1038 break
1038 else:
1039 else:
1039 continue
1040 continue
1040
1041
1041 if node is None:
1042 if node is None:
1042 last = len(filelog) - 1
1043 last = len(filelog) - 1
1043 else:
1044 else:
1044 last = filelog.rev(node)
1045 last = filelog.rev(node)
1045
1046
1046
1047
1047 # keep track of all ancestors of the file
1048 # keep track of all ancestors of the file
1048 ancestors = set([filelog.linkrev(last)])
1049 ancestors = set([filelog.linkrev(last)])
1049
1050
1050 # iterate from latest to oldest revision
1051 # iterate from latest to oldest revision
1051 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1052 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1052 if not follow:
1053 if not follow:
1053 if rev > maxrev:
1054 if rev > maxrev:
1054 continue
1055 continue
1055 else:
1056 else:
1056 # Note that last might not be the first interesting
1057 # Note that last might not be the first interesting
1057 # rev to us:
1058 # rev to us:
1058 # if the file has been changed after maxrev, we'll
1059 # if the file has been changed after maxrev, we'll
1059 # have linkrev(last) > maxrev, and we still need
1060 # have linkrev(last) > maxrev, and we still need
1060 # to explore the file graph
1061 # to explore the file graph
1061 if rev not in ancestors:
1062 if rev not in ancestors:
1062 continue
1063 continue
1063 # XXX insert 1327 fix here
1064 # XXX insert 1327 fix here
1064 if flparentlinkrevs:
1065 if flparentlinkrevs:
1065 ancestors.update(flparentlinkrevs)
1066 ancestors.update(flparentlinkrevs)
1066
1067
1067 fncache.setdefault(rev, []).append(file_)
1068 fncache.setdefault(rev, []).append(file_)
1068 wanted.add(rev)
1069 wanted.add(rev)
1069 if copied:
1070 if copied:
1070 copies.append(copied)
1071 copies.append(copied)
1071 if slowpath:
1072 if slowpath:
1072 # We have to read the changelog to match filenames against
1073 # We have to read the changelog to match filenames against
1073 # changed files
1074 # changed files
1074
1075
1075 if follow:
1076 if follow:
1076 raise util.Abort(_('can only follow copies/renames for explicit '
1077 raise util.Abort(_('can only follow copies/renames for explicit '
1077 'filenames'))
1078 'filenames'))
1078
1079
1079 # The slow path checks files modified in every changeset.
1080 # The slow path checks files modified in every changeset.
1080 for i in sorted(revs):
1081 for i in sorted(revs):
1081 ctx = change(i)
1082 ctx = change(i)
1082 matches = filter(match, ctx.files())
1083 matches = filter(match, ctx.files())
1083 if matches:
1084 if matches:
1084 fncache[i] = matches
1085 fncache[i] = matches
1085 wanted.add(i)
1086 wanted.add(i)
1086
1087
1087 class followfilter(object):
1088 class followfilter(object):
1088 def __init__(self, onlyfirst=False):
1089 def __init__(self, onlyfirst=False):
1089 self.startrev = nullrev
1090 self.startrev = nullrev
1090 self.roots = set()
1091 self.roots = set()
1091 self.onlyfirst = onlyfirst
1092 self.onlyfirst = onlyfirst
1092
1093
1093 def match(self, rev):
1094 def match(self, rev):
1094 def realparents(rev):
1095 def realparents(rev):
1095 if self.onlyfirst:
1096 if self.onlyfirst:
1096 return repo.changelog.parentrevs(rev)[0:1]
1097 return repo.changelog.parentrevs(rev)[0:1]
1097 else:
1098 else:
1098 return filter(lambda x: x != nullrev,
1099 return filter(lambda x: x != nullrev,
1099 repo.changelog.parentrevs(rev))
1100 repo.changelog.parentrevs(rev))
1100
1101
1101 if self.startrev == nullrev:
1102 if self.startrev == nullrev:
1102 self.startrev = rev
1103 self.startrev = rev
1103 return True
1104 return True
1104
1105
1105 if rev > self.startrev:
1106 if rev > self.startrev:
1106 # forward: all descendants
1107 # forward: all descendants
1107 if not self.roots:
1108 if not self.roots:
1108 self.roots.add(self.startrev)
1109 self.roots.add(self.startrev)
1109 for parent in realparents(rev):
1110 for parent in realparents(rev):
1110 if parent in self.roots:
1111 if parent in self.roots:
1111 self.roots.add(rev)
1112 self.roots.add(rev)
1112 return True
1113 return True
1113 else:
1114 else:
1114 # backwards: all parents
1115 # backwards: all parents
1115 if not self.roots:
1116 if not self.roots:
1116 self.roots.update(realparents(self.startrev))
1117 self.roots.update(realparents(self.startrev))
1117 if rev in self.roots:
1118 if rev in self.roots:
1118 self.roots.remove(rev)
1119 self.roots.remove(rev)
1119 self.roots.update(realparents(rev))
1120 self.roots.update(realparents(rev))
1120 return True
1121 return True
1121
1122
1122 return False
1123 return False
1123
1124
1124 # it might be worthwhile to do this in the iterator if the rev range
1125 # it might be worthwhile to do this in the iterator if the rev range
1125 # is descending and the prune args are all within that range
1126 # is descending and the prune args are all within that range
1126 for rev in opts.get('prune', ()):
1127 for rev in opts.get('prune', ()):
1127 rev = repo.changelog.rev(repo.lookup(rev))
1128 rev = repo.changelog.rev(repo.lookup(rev))
1128 ff = followfilter()
1129 ff = followfilter()
1129 stop = min(revs[0], revs[-1])
1130 stop = min(revs[0], revs[-1])
1130 for x in xrange(rev, stop - 1, -1):
1131 for x in xrange(rev, stop - 1, -1):
1131 if ff.match(x):
1132 if ff.match(x):
1132 wanted.discard(x)
1133 wanted.discard(x)
1133
1134
1134 # Now that wanted is correctly initialized, we can iterate over the
1135 # Now that wanted is correctly initialized, we can iterate over the
1135 # revision range, yielding only revisions in wanted.
1136 # revision range, yielding only revisions in wanted.
1136 def iterate():
1137 def iterate():
1137 if follow and not match.files():
1138 if follow and not match.files():
1138 ff = followfilter(onlyfirst=opts.get('follow_first'))
1139 ff = followfilter(onlyfirst=opts.get('follow_first'))
1139 def want(rev):
1140 def want(rev):
1140 return ff.match(rev) and rev in wanted
1141 return ff.match(rev) and rev in wanted
1141 else:
1142 else:
1142 def want(rev):
1143 def want(rev):
1143 return rev in wanted
1144 return rev in wanted
1144
1145
1145 for i, window in increasing_windows(0, len(revs)):
1146 for i, window in increasing_windows(0, len(revs)):
1146 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1147 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1147 for rev in sorted(nrevs):
1148 for rev in sorted(nrevs):
1148 fns = fncache.get(rev)
1149 fns = fncache.get(rev)
1149 ctx = change(rev)
1150 ctx = change(rev)
1150 if not fns:
1151 if not fns:
1151 def fns_generator():
1152 def fns_generator():
1152 for f in ctx.files():
1153 for f in ctx.files():
1153 if match(f):
1154 if match(f):
1154 yield f
1155 yield f
1155 fns = fns_generator()
1156 fns = fns_generator()
1156 prepare(ctx, fns)
1157 prepare(ctx, fns)
1157 for rev in nrevs:
1158 for rev in nrevs:
1158 yield change(rev)
1159 yield change(rev)
1159 return iterate()
1160 return iterate()
1160
1161
1161 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1162 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1162 join = lambda f: os.path.join(prefix, f)
1163 join = lambda f: os.path.join(prefix, f)
1163 bad = []
1164 bad = []
1164 oldbad = match.bad
1165 oldbad = match.bad
1165 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1166 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1166 names = []
1167 names = []
1167 wctx = repo[None]
1168 wctx = repo[None]
1168 cca = None
1169 cca = None
1169 abort, warn = scmutil.checkportabilityalert(ui)
1170 abort, warn = scmutil.checkportabilityalert(ui)
1170 if abort or warn:
1171 if abort or warn:
1171 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1172 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1172 for f in repo.walk(match):
1173 for f in repo.walk(match):
1173 exact = match.exact(f)
1174 exact = match.exact(f)
1174 if exact or f not in repo.dirstate:
1175 if exact or f not in repo.dirstate:
1175 if cca:
1176 if cca:
1176 cca(f)
1177 cca(f)
1177 names.append(f)
1178 names.append(f)
1178 if ui.verbose or not exact:
1179 if ui.verbose or not exact:
1179 ui.status(_('adding %s\n') % match.rel(join(f)))
1180 ui.status(_('adding %s\n') % match.rel(join(f)))
1180
1181
1181 if listsubrepos:
1182 if listsubrepos:
1182 for subpath in wctx.substate:
1183 for subpath in wctx.substate:
1183 sub = wctx.sub(subpath)
1184 sub = wctx.sub(subpath)
1184 try:
1185 try:
1185 submatch = matchmod.narrowmatcher(subpath, match)
1186 submatch = matchmod.narrowmatcher(subpath, match)
1186 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1187 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1187 except error.LookupError:
1188 except error.LookupError:
1188 ui.status(_("skipping missing subrepository: %s\n")
1189 ui.status(_("skipping missing subrepository: %s\n")
1189 % join(subpath))
1190 % join(subpath))
1190
1191
1191 if not dryrun:
1192 if not dryrun:
1192 rejected = wctx.add(names, prefix)
1193 rejected = wctx.add(names, prefix)
1193 bad.extend(f for f in rejected if f in match.files())
1194 bad.extend(f for f in rejected if f in match.files())
1194 return bad
1195 return bad
1195
1196
1196 def duplicatecopies(repo, rev, p1, p2):
1197 def duplicatecopies(repo, rev, p1, p2):
1197 "Reproduce copies found in the source revision in the dirstate for grafts"
1198 "Reproduce copies found in the source revision in the dirstate for grafts"
1198 # Here we simulate the copies and renames in the source changeset
1199 # Here we simulate the copies and renames in the source changeset
1199 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
1200 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
1200 m1 = repo[rev].manifest()
1201 m1 = repo[rev].manifest()
1201 m2 = repo[p1].manifest()
1202 m2 = repo[p1].manifest()
1202 for k, v in cop.iteritems():
1203 for k, v in cop.iteritems():
1203 if k in m1:
1204 if k in m1:
1204 if v in m1 or v in m2:
1205 if v in m1 or v in m2:
1205 repo.dirstate.copy(v, k)
1206 repo.dirstate.copy(v, k)
1206 if v in m2 and v not in m1 and k in m2:
1207 if v in m2 and v not in m1 and k in m2:
1207 repo.dirstate.remove(v)
1208 repo.dirstate.remove(v)
1208
1209
1209 def commit(ui, repo, commitfunc, pats, opts):
1210 def commit(ui, repo, commitfunc, pats, opts):
1210 '''commit the specified files or all outstanding changes'''
1211 '''commit the specified files or all outstanding changes'''
1211 date = opts.get('date')
1212 date = opts.get('date')
1212 if date:
1213 if date:
1213 opts['date'] = util.parsedate(date)
1214 opts['date'] = util.parsedate(date)
1214 message = logmessage(ui, opts)
1215 message = logmessage(ui, opts)
1215
1216
1216 # extract addremove carefully -- this function can be called from a command
1217 # extract addremove carefully -- this function can be called from a command
1217 # that doesn't support addremove
1218 # that doesn't support addremove
1218 if opts.get('addremove'):
1219 if opts.get('addremove'):
1219 scmutil.addremove(repo, pats, opts)
1220 scmutil.addremove(repo, pats, opts)
1220
1221
1221 return commitfunc(ui, repo, message,
1222 return commitfunc(ui, repo, message,
1222 scmutil.match(repo[None], pats, opts), opts)
1223 scmutil.match(repo[None], pats, opts), opts)
1223
1224
1224 def commiteditor(repo, ctx, subs):
1225 def commiteditor(repo, ctx, subs):
1225 if ctx.description():
1226 if ctx.description():
1226 return ctx.description()
1227 return ctx.description()
1227 return commitforceeditor(repo, ctx, subs)
1228 return commitforceeditor(repo, ctx, subs)
1228
1229
1229 def commitforceeditor(repo, ctx, subs):
1230 def commitforceeditor(repo, ctx, subs):
1230 edittext = []
1231 edittext = []
1231 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1232 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1232 if ctx.description():
1233 if ctx.description():
1233 edittext.append(ctx.description())
1234 edittext.append(ctx.description())
1234 edittext.append("")
1235 edittext.append("")
1235 edittext.append("") # Empty line between message and comments.
1236 edittext.append("") # Empty line between message and comments.
1236 edittext.append(_("HG: Enter commit message."
1237 edittext.append(_("HG: Enter commit message."
1237 " Lines beginning with 'HG:' are removed."))
1238 " Lines beginning with 'HG:' are removed."))
1238 edittext.append(_("HG: Leave message empty to abort commit."))
1239 edittext.append(_("HG: Leave message empty to abort commit."))
1239 edittext.append("HG: --")
1240 edittext.append("HG: --")
1240 edittext.append(_("HG: user: %s") % ctx.user())
1241 edittext.append(_("HG: user: %s") % ctx.user())
1241 if ctx.p2():
1242 if ctx.p2():
1242 edittext.append(_("HG: branch merge"))
1243 edittext.append(_("HG: branch merge"))
1243 if ctx.branch():
1244 if ctx.branch():
1244 edittext.append(_("HG: branch '%s'") % ctx.branch())
1245 edittext.append(_("HG: branch '%s'") % ctx.branch())
1245 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1246 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1246 edittext.extend([_("HG: added %s") % f for f in added])
1247 edittext.extend([_("HG: added %s") % f for f in added])
1247 edittext.extend([_("HG: changed %s") % f for f in modified])
1248 edittext.extend([_("HG: changed %s") % f for f in modified])
1248 edittext.extend([_("HG: removed %s") % f for f in removed])
1249 edittext.extend([_("HG: removed %s") % f for f in removed])
1249 if not added and not modified and not removed:
1250 if not added and not modified and not removed:
1250 edittext.append(_("HG: no files changed"))
1251 edittext.append(_("HG: no files changed"))
1251 edittext.append("")
1252 edittext.append("")
1252 # run editor in the repository root
1253 # run editor in the repository root
1253 olddir = os.getcwd()
1254 olddir = os.getcwd()
1254 os.chdir(repo.root)
1255 os.chdir(repo.root)
1255 text = repo.ui.edit("\n".join(edittext), ctx.user())
1256 text = repo.ui.edit("\n".join(edittext), ctx.user())
1256 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1257 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1257 os.chdir(olddir)
1258 os.chdir(olddir)
1258
1259
1259 if not text.strip():
1260 if not text.strip():
1260 raise util.Abort(_("empty commit message"))
1261 raise util.Abort(_("empty commit message"))
1261
1262
1262 return text
1263 return text
1263
1264
1264 def command(table):
1265 def command(table):
1265 '''returns a function object bound to table which can be used as
1266 '''returns a function object bound to table which can be used as
1266 a decorator for populating table as a command table'''
1267 a decorator for populating table as a command table'''
1267
1268
1268 def cmd(name, options, synopsis=None):
1269 def cmd(name, options, synopsis=None):
1269 def decorator(func):
1270 def decorator(func):
1270 if synopsis:
1271 if synopsis:
1271 table[name] = func, options[:], synopsis
1272 table[name] = func, options[:], synopsis
1272 else:
1273 else:
1273 table[name] = func, options[:]
1274 table[name] = func, options[:]
1274 return func
1275 return func
1275 return decorator
1276 return decorator
1276
1277
1277 return cmd
1278 return cmd
General Comments 0
You need to be logged in to leave comments. Login now