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