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