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