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