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