##// END OF EJS Templates
cmdutil: extract repo dependent closures in templatekw
Patrick Mezard -
r10055:e400a511 default
parent child Browse files
Show More
@@ -1,1218 +1,1203 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 # Cache mapping from rev to a tuple with tag date, tag
767 # distance and tag name
767 # distance and tag name
768 self._latesttagcache = {-1: (0, 0, 'null')}
768 self._latesttagcache = {-1: (0, 0, 'null')}
769
769
770 def use_template(self, t):
770 def use_template(self, t):
771 '''set template string to use'''
771 '''set template string to use'''
772 self.t.cache['changeset'] = t
772 self.t.cache['changeset'] = t
773
773
774 def _meaningful_parentrevs(self, ctx):
774 def _meaningful_parentrevs(self, ctx):
775 """Return list of meaningful (or all if debug) parentrevs for rev.
775 """Return list of meaningful (or all if debug) parentrevs for rev.
776 """
776 """
777 parents = ctx.parents()
777 parents = ctx.parents()
778 if len(parents) > 1:
778 if len(parents) > 1:
779 return parents
779 return parents
780 if self.ui.debugflag:
780 if self.ui.debugflag:
781 return [parents[0], self.repo['null']]
781 return [parents[0], self.repo['null']]
782 if parents[0].rev() >= ctx.rev() - 1:
782 if parents[0].rev() >= ctx.rev() - 1:
783 return []
783 return []
784 return parents
784 return parents
785
785
786 def _latesttaginfo(self, rev):
786 def _latesttaginfo(self, rev):
787 '''return date, distance and name for the latest tag of rev'''
787 '''return date, distance and name for the latest tag of rev'''
788 todo = [rev]
788 todo = [rev]
789 while todo:
789 while todo:
790 rev = todo.pop()
790 rev = todo.pop()
791 if rev in self._latesttagcache:
791 if rev in self._latesttagcache:
792 continue
792 continue
793 ctx = self.repo[rev]
793 ctx = self.repo[rev]
794 tags = [t for t in ctx.tags() if self.repo.tagtype(t) == 'global']
794 tags = [t for t in ctx.tags() if self.repo.tagtype(t) == 'global']
795 if tags:
795 if tags:
796 self._latesttagcache[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
796 self._latesttagcache[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
797 continue
797 continue
798 try:
798 try:
799 # The tuples are laid out so the right one can be found by comparison.
799 # The tuples are laid out so the right one can be found by comparison.
800 pdate, pdist, ptag = max(
800 pdate, pdist, ptag = max(
801 self._latesttagcache[p.rev()] for p in ctx.parents())
801 self._latesttagcache[p.rev()] for p in ctx.parents())
802 except KeyError:
802 except KeyError:
803 # Cache miss - recurse
803 # Cache miss - recurse
804 todo.append(rev)
804 todo.append(rev)
805 todo.extend(p.rev() for p in ctx.parents())
805 todo.extend(p.rev() for p in ctx.parents())
806 continue
806 continue
807 self._latesttagcache[rev] = pdate, pdist + 1, ptag
807 self._latesttagcache[rev] = pdate, pdist + 1, ptag
808 return self._latesttagcache[rev]
808 return self._latesttagcache[rev]
809
809
810 def _show(self, ctx, copies, props):
810 def _show(self, ctx, copies, props):
811 '''show a single changeset or file revision'''
811 '''show a single changeset or file revision'''
812
812
813 showlist = templatekw.showlist
813 showlist = templatekw.showlist
814
814
815 def showparents(ctx, templ, **args):
815 def showparents(repo, ctx, templ, **args):
816 parents = [[('rev', p.rev()), ('node', p.hex())]
816 parents = [[('rev', p.rev()), ('node', p.hex())]
817 for p in self._meaningful_parentrevs(ctx)]
817 for p in self._meaningful_parentrevs(ctx)]
818 return showlist(templ, 'parent', parents, **args)
818 return showlist(templ, 'parent', parents, **args)
819
819
820 def showcopies(ctx, templ, **args):
820 def showcopies(repo, ctx, templ, **args):
821 c = [{'name': x[0], 'source': x[1]} for x in copies]
821 c = [{'name': x[0], 'source': x[1]} for x in copies]
822 return showlist(templ, 'file_copy', c, plural='file_copies', **args)
822 return showlist(templ, 'file_copy', c, plural='file_copies', **args)
823
823
824 files = []
824 files = []
825 def getfiles():
825 def getfiles():
826 if not files:
826 if not files:
827 files[:] = self.repo.status(ctx.parents()[0].node(),
827 files[:] = self.repo.status(ctx.parents()[0].node(),
828 ctx.node())[:3]
828 ctx.node())[:3]
829 return files
829 return files
830 def showmods(ctx, templ, **args):
830 def showmods(repo, ctx, templ, **args):
831 return showlist(templ, 'file_mod', getfiles()[0], **args)
831 return showlist(templ, 'file_mod', getfiles()[0], **args)
832 def showadds(ctx, templ, **args):
832 def showadds(repo, ctx, templ, **args):
833 return showlist(templ, 'file_add', getfiles()[1], **args)
833 return showlist(templ, 'file_add', getfiles()[1], **args)
834 def showdels(ctx, templ, **args):
834 def showdels(repo, ctx, templ, **args):
835 return showlist(templ, 'file_del', getfiles()[2], **args)
835 return showlist(templ, 'file_del', getfiles()[2], **args)
836 def showmanifest(ctx, templ, **args):
836
837 args = args.copy()
837 def showlatesttag(repo, ctx, templ, **args):
838 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
839 node=hex(ctx.changeset()[0])))
840 return templ('manifest', **args)
841
842 def showdiffstat(ctx, templ, **args):
843 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
844 files, adds, removes = 0, 0, 0
845 for i in patch.diffstatdata(util.iterlines(diff)):
846 files += 1
847 adds += i[1]
848 removes += i[2]
849 return '%s: +%s/-%s' % (files, adds, removes)
850
851 def showlatesttag(ctx, templ, **args):
852 return self._latesttaginfo(ctx.rev())[2]
838 return self._latesttaginfo(ctx.rev())[2]
853 def showlatesttagdistance(ctx, templ, **args):
839 def showlatesttagdistance(repo, ctx, templ, **args):
854 return self._latesttaginfo(ctx.rev())[1]
840 return self._latesttaginfo(ctx.rev())[1]
855
841
856 defprops = {
842 defprops = {
857 'file_adds': showadds,
843 'file_adds': showadds,
858 'file_dels': showdels,
844 'file_dels': showdels,
859 'file_mods': showmods,
845 'file_mods': showmods,
860 'file_copies': showcopies,
846 'file_copies': showcopies,
861 'manifest': showmanifest,
847 'parents': showparents,
862 'parents': showparents,
863 'diffstat': showdiffstat,
864 'latesttag': showlatesttag,
848 'latesttag': showlatesttag,
865 'latesttagdistance': showlatesttagdistance,
849 'latesttagdistance': showlatesttagdistance,
866 }
850 }
867 props = props.copy()
851 props = props.copy()
868 props.update(templatekw.keywords)
852 props.update(templatekw.keywords)
869 props.update(defprops)
853 props.update(defprops)
870 props['templ'] = self.t
854 props['templ'] = self.t
871 props['ctx'] = ctx
855 props['ctx'] = ctx
856 props['repo'] = self.repo
872
857
873 # find correct templates for current mode
858 # find correct templates for current mode
874
859
875 tmplmodes = [
860 tmplmodes = [
876 (True, None),
861 (True, None),
877 (self.ui.verbose, 'verbose'),
862 (self.ui.verbose, 'verbose'),
878 (self.ui.quiet, 'quiet'),
863 (self.ui.quiet, 'quiet'),
879 (self.ui.debugflag, 'debug'),
864 (self.ui.debugflag, 'debug'),
880 ]
865 ]
881
866
882 types = {'header': '', 'changeset': 'changeset'}
867 types = {'header': '', 'changeset': 'changeset'}
883 for mode, postfix in tmplmodes:
868 for mode, postfix in tmplmodes:
884 for type in types:
869 for type in types:
885 cur = postfix and ('%s_%s' % (type, postfix)) or type
870 cur = postfix and ('%s_%s' % (type, postfix)) or type
886 if mode and cur in self.t:
871 if mode and cur in self.t:
887 types[type] = cur
872 types[type] = cur
888
873
889 try:
874 try:
890
875
891 # write header
876 # write header
892 if types['header']:
877 if types['header']:
893 h = templater.stringify(self.t(types['header'], **props))
878 h = templater.stringify(self.t(types['header'], **props))
894 if self.buffered:
879 if self.buffered:
895 self.header[ctx.rev()] = h
880 self.header[ctx.rev()] = h
896 else:
881 else:
897 self.ui.write(h)
882 self.ui.write(h)
898
883
899 # write changeset metadata, then patch if requested
884 # write changeset metadata, then patch if requested
900 key = types['changeset']
885 key = types['changeset']
901 self.ui.write(templater.stringify(self.t(key, **props)))
886 self.ui.write(templater.stringify(self.t(key, **props)))
902 self.showpatch(ctx.node())
887 self.showpatch(ctx.node())
903
888
904 except KeyError, inst:
889 except KeyError, inst:
905 msg = _("%s: no key named '%s'")
890 msg = _("%s: no key named '%s'")
906 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
891 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
907 except SyntaxError, inst:
892 except SyntaxError, inst:
908 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
893 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
909
894
910 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
895 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
911 """show one changeset using template or regular display.
896 """show one changeset using template or regular display.
912
897
913 Display format will be the first non-empty hit of:
898 Display format will be the first non-empty hit of:
914 1. option 'template'
899 1. option 'template'
915 2. option 'style'
900 2. option 'style'
916 3. [ui] setting 'logtemplate'
901 3. [ui] setting 'logtemplate'
917 4. [ui] setting 'style'
902 4. [ui] setting 'style'
918 If all of these values are either the unset or the empty string,
903 If all of these values are either the unset or the empty string,
919 regular display via changeset_printer() is done.
904 regular display via changeset_printer() is done.
920 """
905 """
921 # options
906 # options
922 patch = False
907 patch = False
923 if opts.get('patch'):
908 if opts.get('patch'):
924 patch = matchfn or matchall(repo)
909 patch = matchfn or matchall(repo)
925
910
926 tmpl = opts.get('template')
911 tmpl = opts.get('template')
927 style = None
912 style = None
928 if tmpl:
913 if tmpl:
929 tmpl = templater.parsestring(tmpl, quoted=False)
914 tmpl = templater.parsestring(tmpl, quoted=False)
930 else:
915 else:
931 style = opts.get('style')
916 style = opts.get('style')
932
917
933 # ui settings
918 # ui settings
934 if not (tmpl or style):
919 if not (tmpl or style):
935 tmpl = ui.config('ui', 'logtemplate')
920 tmpl = ui.config('ui', 'logtemplate')
936 if tmpl:
921 if tmpl:
937 tmpl = templater.parsestring(tmpl)
922 tmpl = templater.parsestring(tmpl)
938 else:
923 else:
939 style = ui.config('ui', 'style')
924 style = ui.config('ui', 'style')
940
925
941 if not (tmpl or style):
926 if not (tmpl or style):
942 return changeset_printer(ui, repo, patch, opts, buffered)
927 return changeset_printer(ui, repo, patch, opts, buffered)
943
928
944 mapfile = None
929 mapfile = None
945 if style and not tmpl:
930 if style and not tmpl:
946 mapfile = style
931 mapfile = style
947 if not os.path.split(mapfile)[0]:
932 if not os.path.split(mapfile)[0]:
948 mapname = (templater.templatepath('map-cmdline.' + mapfile)
933 mapname = (templater.templatepath('map-cmdline.' + mapfile)
949 or templater.templatepath(mapfile))
934 or templater.templatepath(mapfile))
950 if mapname: mapfile = mapname
935 if mapname: mapfile = mapname
951
936
952 try:
937 try:
953 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
938 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
954 except SyntaxError, inst:
939 except SyntaxError, inst:
955 raise util.Abort(inst.args[0])
940 raise util.Abort(inst.args[0])
956 if tmpl: t.use_template(tmpl)
941 if tmpl: t.use_template(tmpl)
957 return t
942 return t
958
943
959 def finddate(ui, repo, date):
944 def finddate(ui, repo, date):
960 """Find the tipmost changeset that matches the given date spec"""
945 """Find the tipmost changeset that matches the given date spec"""
961
946
962 df = util.matchdate(date)
947 df = util.matchdate(date)
963 m = matchall(repo)
948 m = matchall(repo)
964 results = {}
949 results = {}
965
950
966 def prep(ctx, fns):
951 def prep(ctx, fns):
967 d = ctx.date()
952 d = ctx.date()
968 if df(d[0]):
953 if df(d[0]):
969 results[ctx.rev()] = d
954 results[ctx.rev()] = d
970
955
971 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
956 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
972 rev = ctx.rev()
957 rev = ctx.rev()
973 if rev in results:
958 if rev in results:
974 ui.status(_("Found revision %s from %s\n") %
959 ui.status(_("Found revision %s from %s\n") %
975 (rev, util.datestr(results[rev])))
960 (rev, util.datestr(results[rev])))
976 return str(rev)
961 return str(rev)
977
962
978 raise util.Abort(_("revision matching date not found"))
963 raise util.Abort(_("revision matching date not found"))
979
964
980 def walkchangerevs(repo, match, opts, prepare):
965 def walkchangerevs(repo, match, opts, prepare):
981 '''Iterate over files and the revs in which they changed.
966 '''Iterate over files and the revs in which they changed.
982
967
983 Callers most commonly need to iterate backwards over the history
968 Callers most commonly need to iterate backwards over the history
984 in which they are interested. Doing so has awful (quadratic-looking)
969 in which they are interested. Doing so has awful (quadratic-looking)
985 performance, so we use iterators in a "windowed" way.
970 performance, so we use iterators in a "windowed" way.
986
971
987 We walk a window of revisions in the desired order. Within the
972 We walk a window of revisions in the desired order. Within the
988 window, we first walk forwards to gather data, then in the desired
973 window, we first walk forwards to gather data, then in the desired
989 order (usually backwards) to display it.
974 order (usually backwards) to display it.
990
975
991 This function returns an iterator yielding contexts. Before
976 This function returns an iterator yielding contexts. Before
992 yielding each context, the iterator will first call the prepare
977 yielding each context, the iterator will first call the prepare
993 function on each context in the window in forward order.'''
978 function on each context in the window in forward order.'''
994
979
995 def increasing_windows(start, end, windowsize=8, sizelimit=512):
980 def increasing_windows(start, end, windowsize=8, sizelimit=512):
996 if start < end:
981 if start < end:
997 while start < end:
982 while start < end:
998 yield start, min(windowsize, end-start)
983 yield start, min(windowsize, end-start)
999 start += windowsize
984 start += windowsize
1000 if windowsize < sizelimit:
985 if windowsize < sizelimit:
1001 windowsize *= 2
986 windowsize *= 2
1002 else:
987 else:
1003 while start > end:
988 while start > end:
1004 yield start, min(windowsize, start-end-1)
989 yield start, min(windowsize, start-end-1)
1005 start -= windowsize
990 start -= windowsize
1006 if windowsize < sizelimit:
991 if windowsize < sizelimit:
1007 windowsize *= 2
992 windowsize *= 2
1008
993
1009 follow = opts.get('follow') or opts.get('follow_first')
994 follow = opts.get('follow') or opts.get('follow_first')
1010
995
1011 if not len(repo):
996 if not len(repo):
1012 return []
997 return []
1013
998
1014 if follow:
999 if follow:
1015 defrange = '%s:0' % repo['.'].rev()
1000 defrange = '%s:0' % repo['.'].rev()
1016 else:
1001 else:
1017 defrange = '-1:0'
1002 defrange = '-1:0'
1018 revs = revrange(repo, opts['rev'] or [defrange])
1003 revs = revrange(repo, opts['rev'] or [defrange])
1019 wanted = set()
1004 wanted = set()
1020 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1005 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1021 fncache = {}
1006 fncache = {}
1022 change = util.cachefunc(repo.changectx)
1007 change = util.cachefunc(repo.changectx)
1023
1008
1024 if not slowpath and not match.files():
1009 if not slowpath and not match.files():
1025 # No files, no patterns. Display all revs.
1010 # No files, no patterns. Display all revs.
1026 wanted = set(revs)
1011 wanted = set(revs)
1027 copies = []
1012 copies = []
1028
1013
1029 if not slowpath:
1014 if not slowpath:
1030 # Only files, no patterns. Check the history of each file.
1015 # Only files, no patterns. Check the history of each file.
1031 def filerevgen(filelog, node):
1016 def filerevgen(filelog, node):
1032 cl_count = len(repo)
1017 cl_count = len(repo)
1033 if node is None:
1018 if node is None:
1034 last = len(filelog) - 1
1019 last = len(filelog) - 1
1035 else:
1020 else:
1036 last = filelog.rev(node)
1021 last = filelog.rev(node)
1037 for i, window in increasing_windows(last, nullrev):
1022 for i, window in increasing_windows(last, nullrev):
1038 revs = []
1023 revs = []
1039 for j in xrange(i - window, i + 1):
1024 for j in xrange(i - window, i + 1):
1040 n = filelog.node(j)
1025 n = filelog.node(j)
1041 revs.append((filelog.linkrev(j),
1026 revs.append((filelog.linkrev(j),
1042 follow and filelog.renamed(n)))
1027 follow and filelog.renamed(n)))
1043 for rev in reversed(revs):
1028 for rev in reversed(revs):
1044 # only yield rev for which we have the changelog, it can
1029 # only yield rev for which we have the changelog, it can
1045 # happen while doing "hg log" during a pull or commit
1030 # happen while doing "hg log" during a pull or commit
1046 if rev[0] < cl_count:
1031 if rev[0] < cl_count:
1047 yield rev
1032 yield rev
1048 def iterfiles():
1033 def iterfiles():
1049 for filename in match.files():
1034 for filename in match.files():
1050 yield filename, None
1035 yield filename, None
1051 for filename_node in copies:
1036 for filename_node in copies:
1052 yield filename_node
1037 yield filename_node
1053 minrev, maxrev = min(revs), max(revs)
1038 minrev, maxrev = min(revs), max(revs)
1054 for file_, node in iterfiles():
1039 for file_, node in iterfiles():
1055 filelog = repo.file(file_)
1040 filelog = repo.file(file_)
1056 if not len(filelog):
1041 if not len(filelog):
1057 if node is None:
1042 if node is None:
1058 # A zero count may be a directory or deleted file, so
1043 # A zero count may be a directory or deleted file, so
1059 # try to find matching entries on the slow path.
1044 # try to find matching entries on the slow path.
1060 if follow:
1045 if follow:
1061 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1046 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1062 slowpath = True
1047 slowpath = True
1063 break
1048 break
1064 else:
1049 else:
1065 continue
1050 continue
1066 for rev, copied in filerevgen(filelog, node):
1051 for rev, copied in filerevgen(filelog, node):
1067 if rev <= maxrev:
1052 if rev <= maxrev:
1068 if rev < minrev:
1053 if rev < minrev:
1069 break
1054 break
1070 fncache.setdefault(rev, [])
1055 fncache.setdefault(rev, [])
1071 fncache[rev].append(file_)
1056 fncache[rev].append(file_)
1072 wanted.add(rev)
1057 wanted.add(rev)
1073 if follow and copied:
1058 if follow and copied:
1074 copies.append(copied)
1059 copies.append(copied)
1075 if slowpath:
1060 if slowpath:
1076 if follow:
1061 if follow:
1077 raise util.Abort(_('can only follow copies/renames for explicit '
1062 raise util.Abort(_('can only follow copies/renames for explicit '
1078 'filenames'))
1063 'filenames'))
1079
1064
1080 # The slow path checks files modified in every changeset.
1065 # The slow path checks files modified in every changeset.
1081 def changerevgen():
1066 def changerevgen():
1082 for i, window in increasing_windows(len(repo) - 1, nullrev):
1067 for i, window in increasing_windows(len(repo) - 1, nullrev):
1083 for j in xrange(i - window, i + 1):
1068 for j in xrange(i - window, i + 1):
1084 yield change(j)
1069 yield change(j)
1085
1070
1086 for ctx in changerevgen():
1071 for ctx in changerevgen():
1087 matches = filter(match, ctx.files())
1072 matches = filter(match, ctx.files())
1088 if matches:
1073 if matches:
1089 fncache[ctx.rev()] = matches
1074 fncache[ctx.rev()] = matches
1090 wanted.add(ctx.rev())
1075 wanted.add(ctx.rev())
1091
1076
1092 class followfilter(object):
1077 class followfilter(object):
1093 def __init__(self, onlyfirst=False):
1078 def __init__(self, onlyfirst=False):
1094 self.startrev = nullrev
1079 self.startrev = nullrev
1095 self.roots = set()
1080 self.roots = set()
1096 self.onlyfirst = onlyfirst
1081 self.onlyfirst = onlyfirst
1097
1082
1098 def match(self, rev):
1083 def match(self, rev):
1099 def realparents(rev):
1084 def realparents(rev):
1100 if self.onlyfirst:
1085 if self.onlyfirst:
1101 return repo.changelog.parentrevs(rev)[0:1]
1086 return repo.changelog.parentrevs(rev)[0:1]
1102 else:
1087 else:
1103 return filter(lambda x: x != nullrev,
1088 return filter(lambda x: x != nullrev,
1104 repo.changelog.parentrevs(rev))
1089 repo.changelog.parentrevs(rev))
1105
1090
1106 if self.startrev == nullrev:
1091 if self.startrev == nullrev:
1107 self.startrev = rev
1092 self.startrev = rev
1108 return True
1093 return True
1109
1094
1110 if rev > self.startrev:
1095 if rev > self.startrev:
1111 # forward: all descendants
1096 # forward: all descendants
1112 if not self.roots:
1097 if not self.roots:
1113 self.roots.add(self.startrev)
1098 self.roots.add(self.startrev)
1114 for parent in realparents(rev):
1099 for parent in realparents(rev):
1115 if parent in self.roots:
1100 if parent in self.roots:
1116 self.roots.add(rev)
1101 self.roots.add(rev)
1117 return True
1102 return True
1118 else:
1103 else:
1119 # backwards: all parents
1104 # backwards: all parents
1120 if not self.roots:
1105 if not self.roots:
1121 self.roots.update(realparents(self.startrev))
1106 self.roots.update(realparents(self.startrev))
1122 if rev in self.roots:
1107 if rev in self.roots:
1123 self.roots.remove(rev)
1108 self.roots.remove(rev)
1124 self.roots.update(realparents(rev))
1109 self.roots.update(realparents(rev))
1125 return True
1110 return True
1126
1111
1127 return False
1112 return False
1128
1113
1129 # it might be worthwhile to do this in the iterator if the rev range
1114 # it might be worthwhile to do this in the iterator if the rev range
1130 # is descending and the prune args are all within that range
1115 # is descending and the prune args are all within that range
1131 for rev in opts.get('prune', ()):
1116 for rev in opts.get('prune', ()):
1132 rev = repo.changelog.rev(repo.lookup(rev))
1117 rev = repo.changelog.rev(repo.lookup(rev))
1133 ff = followfilter()
1118 ff = followfilter()
1134 stop = min(revs[0], revs[-1])
1119 stop = min(revs[0], revs[-1])
1135 for x in xrange(rev, stop-1, -1):
1120 for x in xrange(rev, stop-1, -1):
1136 if ff.match(x):
1121 if ff.match(x):
1137 wanted.discard(x)
1122 wanted.discard(x)
1138
1123
1139 def iterate():
1124 def iterate():
1140 if follow and not match.files():
1125 if follow and not match.files():
1141 ff = followfilter(onlyfirst=opts.get('follow_first'))
1126 ff = followfilter(onlyfirst=opts.get('follow_first'))
1142 def want(rev):
1127 def want(rev):
1143 return ff.match(rev) and rev in wanted
1128 return ff.match(rev) and rev in wanted
1144 else:
1129 else:
1145 def want(rev):
1130 def want(rev):
1146 return rev in wanted
1131 return rev in wanted
1147
1132
1148 for i, window in increasing_windows(0, len(revs)):
1133 for i, window in increasing_windows(0, len(revs)):
1149 change = util.cachefunc(repo.changectx)
1134 change = util.cachefunc(repo.changectx)
1150 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1135 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1151 for rev in sorted(nrevs):
1136 for rev in sorted(nrevs):
1152 fns = fncache.get(rev)
1137 fns = fncache.get(rev)
1153 ctx = change(rev)
1138 ctx = change(rev)
1154 if not fns:
1139 if not fns:
1155 def fns_generator():
1140 def fns_generator():
1156 for f in ctx.files():
1141 for f in ctx.files():
1157 if match(f):
1142 if match(f):
1158 yield f
1143 yield f
1159 fns = fns_generator()
1144 fns = fns_generator()
1160 prepare(ctx, fns)
1145 prepare(ctx, fns)
1161 for rev in nrevs:
1146 for rev in nrevs:
1162 yield change(rev)
1147 yield change(rev)
1163 return iterate()
1148 return iterate()
1164
1149
1165 def commit(ui, repo, commitfunc, pats, opts):
1150 def commit(ui, repo, commitfunc, pats, opts):
1166 '''commit the specified files or all outstanding changes'''
1151 '''commit the specified files or all outstanding changes'''
1167 date = opts.get('date')
1152 date = opts.get('date')
1168 if date:
1153 if date:
1169 opts['date'] = util.parsedate(date)
1154 opts['date'] = util.parsedate(date)
1170 message = logmessage(opts)
1155 message = logmessage(opts)
1171
1156
1172 # extract addremove carefully -- this function can be called from a command
1157 # extract addremove carefully -- this function can be called from a command
1173 # that doesn't support addremove
1158 # that doesn't support addremove
1174 if opts.get('addremove'):
1159 if opts.get('addremove'):
1175 addremove(repo, pats, opts)
1160 addremove(repo, pats, opts)
1176
1161
1177 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1162 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1178
1163
1179 def commiteditor(repo, ctx, subs):
1164 def commiteditor(repo, ctx, subs):
1180 if ctx.description():
1165 if ctx.description():
1181 return ctx.description()
1166 return ctx.description()
1182 return commitforceeditor(repo, ctx, subs)
1167 return commitforceeditor(repo, ctx, subs)
1183
1168
1184 def commitforceeditor(repo, ctx, subs):
1169 def commitforceeditor(repo, ctx, subs):
1185 edittext = []
1170 edittext = []
1186 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1171 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1187 if ctx.description():
1172 if ctx.description():
1188 edittext.append(ctx.description())
1173 edittext.append(ctx.description())
1189 edittext.append("")
1174 edittext.append("")
1190 edittext.append("") # Empty line between message and comments.
1175 edittext.append("") # Empty line between message and comments.
1191 edittext.append(_("HG: Enter commit message."
1176 edittext.append(_("HG: Enter commit message."
1192 " Lines beginning with 'HG:' are removed."))
1177 " Lines beginning with 'HG:' are removed."))
1193 edittext.append(_("HG: Leave message empty to abort commit."))
1178 edittext.append(_("HG: Leave message empty to abort commit."))
1194 edittext.append("HG: --")
1179 edittext.append("HG: --")
1195 edittext.append(_("HG: user: %s") % ctx.user())
1180 edittext.append(_("HG: user: %s") % ctx.user())
1196 if ctx.p2():
1181 if ctx.p2():
1197 edittext.append(_("HG: branch merge"))
1182 edittext.append(_("HG: branch merge"))
1198 if ctx.branch():
1183 if ctx.branch():
1199 edittext.append(_("HG: branch '%s'")
1184 edittext.append(_("HG: branch '%s'")
1200 % encoding.tolocal(ctx.branch()))
1185 % encoding.tolocal(ctx.branch()))
1201 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1186 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1202 edittext.extend([_("HG: added %s") % f for f in added])
1187 edittext.extend([_("HG: added %s") % f for f in added])
1203 edittext.extend([_("HG: changed %s") % f for f in modified])
1188 edittext.extend([_("HG: changed %s") % f for f in modified])
1204 edittext.extend([_("HG: removed %s") % f for f in removed])
1189 edittext.extend([_("HG: removed %s") % f for f in removed])
1205 if not added and not modified and not removed:
1190 if not added and not modified and not removed:
1206 edittext.append(_("HG: no files changed"))
1191 edittext.append(_("HG: no files changed"))
1207 edittext.append("")
1192 edittext.append("")
1208 # run editor in the repository root
1193 # run editor in the repository root
1209 olddir = os.getcwd()
1194 olddir = os.getcwd()
1210 os.chdir(repo.root)
1195 os.chdir(repo.root)
1211 text = repo.ui.edit("\n".join(edittext), ctx.user())
1196 text = repo.ui.edit("\n".join(edittext), ctx.user())
1212 text = re.sub("(?m)^HG:.*\n", "", text)
1197 text = re.sub("(?m)^HG:.*\n", "", text)
1213 os.chdir(olddir)
1198 os.chdir(olddir)
1214
1199
1215 if not text.strip():
1200 if not text.strip():
1216 raise util.Abort(_("empty commit message"))
1201 raise util.Abort(_("empty commit message"))
1217
1202
1218 return text
1203 return text
@@ -1,115 +1,133 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 import encoding
8 from node import hex
9 import encoding, patch, util
9
10
10 def showlist(templ, name, values, plural=None, **args):
11 def showlist(templ, name, values, plural=None, **args):
11 '''expand set of values.
12 '''expand set of values.
12 name is name of key in template map.
13 name is name of key in template map.
13 values is list of strings or dicts.
14 values is list of strings or dicts.
14 plural is plural of name, if not simply name + 's'.
15 plural is plural of name, if not simply name + 's'.
15
16
16 expansion works like this, given name 'foo'.
17 expansion works like this, given name 'foo'.
17
18
18 if values is empty, expand 'no_foos'.
19 if values is empty, expand 'no_foos'.
19
20
20 if 'foo' not in template map, return values as a string,
21 if 'foo' not in template map, return values as a string,
21 joined by space.
22 joined by space.
22
23
23 expand 'start_foos'.
24 expand 'start_foos'.
24
25
25 for each value, expand 'foo'. if 'last_foo' in template
26 for each value, expand 'foo'. if 'last_foo' in template
26 map, expand it instead of 'foo' for last key.
27 map, expand it instead of 'foo' for last key.
27
28
28 expand 'end_foos'.
29 expand 'end_foos'.
29 '''
30 '''
30 if plural: names = plural
31 if plural: names = plural
31 else: names = name + 's'
32 else: names = name + 's'
32 if not values:
33 if not values:
33 noname = 'no_' + names
34 noname = 'no_' + names
34 if noname in templ:
35 if noname in templ:
35 yield templ(noname, **args)
36 yield templ(noname, **args)
36 return
37 return
37 if name not in templ:
38 if name not in templ:
38 if isinstance(values[0], str):
39 if isinstance(values[0], str):
39 yield ' '.join(values)
40 yield ' '.join(values)
40 else:
41 else:
41 for v in values:
42 for v in values:
42 yield dict(v, **args)
43 yield dict(v, **args)
43 return
44 return
44 startname = 'start_' + names
45 startname = 'start_' + names
45 if startname in templ:
46 if startname in templ:
46 yield templ(startname, **args)
47 yield templ(startname, **args)
47 vargs = args.copy()
48 vargs = args.copy()
48 def one(v, tag=name):
49 def one(v, tag=name):
49 try:
50 try:
50 vargs.update(v)
51 vargs.update(v)
51 except (AttributeError, ValueError):
52 except (AttributeError, ValueError):
52 try:
53 try:
53 for a, b in v:
54 for a, b in v:
54 vargs[a] = b
55 vargs[a] = b
55 except ValueError:
56 except ValueError:
56 vargs[name] = v
57 vargs[name] = v
57 return templ(tag, **vargs)
58 return templ(tag, **vargs)
58 lastname = 'last_' + name
59 lastname = 'last_' + name
59 if lastname in templ:
60 if lastname in templ:
60 last = values.pop()
61 last = values.pop()
61 else:
62 else:
62 last = None
63 last = None
63 for v in values:
64 for v in values:
64 yield one(v)
65 yield one(v)
65 if last is not None:
66 if last is not None:
66 yield one(last, tag=lastname)
67 yield one(last, tag=lastname)
67 endname = 'end_' + names
68 endname = 'end_' + names
68 if endname in templ:
69 if endname in templ:
69 yield templ(endname, **args)
70 yield templ(endname, **args)
70
71
71 def showauthor(ctx, templ, **args):
72 def showauthor(repo, ctx, templ, **args):
72 return ctx.user()
73 return ctx.user()
73
74
74 def showbranches(ctx, templ, **args):
75 def showbranches(repo, ctx, templ, **args):
75 branch = ctx.branch()
76 branch = ctx.branch()
76 if branch != 'default':
77 if branch != 'default':
77 branch = encoding.tolocal(branch)
78 branch = encoding.tolocal(branch)
78 return showlist(templ, 'branch', [branch], plural='branches', **args)
79 return showlist(templ, 'branch', [branch], plural='branches', **args)
79
80
80 def showdate(ctx, templ, **args):
81 def showdate(repo, ctx, templ, **args):
81 return ctx.date()
82 return ctx.date()
82
83
83 def showdescription(ctx, templ, **args):
84 def showdescription(repo, ctx, templ, **args):
84 return ctx.description().strip()
85 return ctx.description().strip()
85
86
86 def showextras(ctx, templ, **args):
87 def showdiffstat(repo, ctx, templ, **args):
88 diff = patch.diff(repo, ctx.parents()[0].node(), ctx.node())
89 files, adds, removes = 0, 0, 0
90 for i in patch.diffstatdata(util.iterlines(diff)):
91 files += 1
92 adds += i[1]
93 removes += i[2]
94 return '%s: +%s/-%s' % (files, adds, removes)
95
96 def showextras(repo, ctx, templ, **args):
87 for key, value in sorted(ctx.extra().items()):
97 for key, value in sorted(ctx.extra().items()):
88 args = args.copy()
98 args = args.copy()
89 args.update(dict(key=key, value=value))
99 args.update(dict(key=key, value=value))
90 yield templ('extra', **args)
100 yield templ('extra', **args)
91
101
92 def showfiles(ctx, templ, **args):
102 def showfiles(repo, ctx, templ, **args):
93 return showlist(templ, 'file', ctx.files(), **args)
103 return showlist(templ, 'file', ctx.files(), **args)
94
104
95 def shownode(ctx, templ, **args):
105 def showmanifest(repo, ctx, templ, **args):
106 args = args.copy()
107 args.update(dict(rev=repo.manifest.rev(ctx.changeset()[0]),
108 node=hex(ctx.changeset()[0])))
109 return templ('manifest', **args)
110
111 def shownode(repo, ctx, templ, **args):
96 return ctx.hex()
112 return ctx.hex()
97
113
98 def showrev(ctx, templ, **args):
114 def showrev(repo, ctx, templ, **args):
99 return ctx.rev()
115 return ctx.rev()
100
116
101 def showtags(ctx, templ, **args):
117 def showtags(repo, ctx, templ, **args):
102 return showlist(templ, 'tag', ctx.tags(), **args)
118 return showlist(templ, 'tag', ctx.tags(), **args)
103
119
104 keywords = {
120 keywords = {
105 'author': showauthor,
121 'author': showauthor,
106 'branches': showbranches,
122 'branches': showbranches,
107 'date': showdate,
123 'date': showdate,
108 'desc': showdescription,
124 'desc': showdescription,
125 'diffstat': showdiffstat,
109 'extras': showextras,
126 'extras': showextras,
110 'files': showfiles,
127 'files': showfiles,
128 'manifest': showmanifest,
111 'node': shownode,
129 'node': shownode,
112 'rev': showrev,
130 'rev': showrev,
113 'tags': showtags,
131 'tags': showtags,
114 }
132 }
115
133
@@ -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'):
14 if k in ('templ', 'ctx', 'repo'):
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