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