##// END OF EJS Templates
cmdutil: replace showlist() closure with a function
Patrick Mezard -
r10053:5c5c6295 default
parent child Browse files
Show More
@@ -0,0 +1,69 b''
1 # templatekw.py - common changeset template keywords
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
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.
7
8
9 def showlist(templ, name, values, plural=None, **args):
10 '''expand set of values.
11 name is name of key in template map.
12 values is list of strings or dicts.
13 plural is plural of name, if not simply name + 's'.
14
15 expansion works like this, given name 'foo'.
16
17 if values is empty, expand 'no_foos'.
18
19 if 'foo' not in template map, return values as a string,
20 joined by space.
21
22 expand 'start_foos'.
23
24 for each value, expand 'foo'. if 'last_foo' in template
25 map, expand it instead of 'foo' for last key.
26
27 expand 'end_foos'.
28 '''
29 if plural: names = plural
30 else: names = name + 's'
31 if not values:
32 noname = 'no_' + names
33 if noname in templ:
34 yield templ(noname, **args)
35 return
36 if name not in templ:
37 if isinstance(values[0], str):
38 yield ' '.join(values)
39 else:
40 for v in values:
41 yield dict(v, **args)
42 return
43 startname = 'start_' + names
44 if startname in templ:
45 yield templ(startname, **args)
46 vargs = args.copy()
47 def one(v, tag=name):
48 try:
49 vargs.update(v)
50 except (AttributeError, ValueError):
51 try:
52 for a, b in v:
53 vargs[a] = b
54 except ValueError:
55 vargs[name] = v
56 return templ(tag, **vargs)
57 lastname = 'last_' + name
58 if lastname in templ:
59 last = values.pop()
60 else:
61 last = None
62 for v in values:
63 yield one(v)
64 if last is not None:
65 yield one(last, tag=lastname)
66 endname = 'end_' + names
67 if endname in templ:
68 yield templ(endname, **args)
69
@@ -1,1300 +1,1243 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
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 def showlist(name, values, plural=None, **args):
813 showlist = templatekw.showlist
814 '''expand set of values.
815 name is name of key in template map.
816 values is list of strings or dicts.
817 plural is plural of name, if not simply name + 's'.
818
819 expansion works like this, given name 'foo'.
820
821 if values is empty, expand 'no_foos'.
822
823 if 'foo' not in template map, return values as a string,
824 joined by space.
825
826 expand 'start_foos'.
827
828 for each value, expand 'foo'. if 'last_foo' in template
829 map, expand it instead of 'foo' for last key.
830
814
831 expand 'end_foos'.
815 def showbranches(templ, **args):
832 '''
833 if plural: names = plural
834 else: names = name + 's'
835 if not values:
836 noname = 'no_' + names
837 if noname in self.t:
838 yield self.t(noname, **args)
839 return
840 if name not in self.t:
841 if isinstance(values[0], str):
842 yield ' '.join(values)
843 else:
844 for v in values:
845 yield dict(v, **args)
846 return
847 startname = 'start_' + names
848 if startname in self.t:
849 yield self.t(startname, **args)
850 vargs = args.copy()
851 def one(v, tag=name):
852 try:
853 vargs.update(v)
854 except (AttributeError, ValueError):
855 try:
856 for a, b in v:
857 vargs[a] = b
858 except ValueError:
859 vargs[name] = v
860 return self.t(tag, **vargs)
861 lastname = 'last_' + name
862 if lastname in self.t:
863 last = values.pop()
864 else:
865 last = None
866 for v in values:
867 yield one(v)
868 if last is not None:
869 yield one(last, tag=lastname)
870 endname = 'end_' + names
871 if endname in self.t:
872 yield self.t(endname, **args)
873
874 def showbranches(**args):
875 branch = ctx.branch()
816 branch = ctx.branch()
876 if branch != 'default':
817 if branch != 'default':
877 branch = encoding.tolocal(branch)
818 branch = encoding.tolocal(branch)
878 return showlist('branch', [branch], plural='branches', **args)
819 return showlist(templ, 'branch', [branch], plural='branches',
820 **args)
879
821
880 def showparents(**args):
822 def showparents(templ, **args):
881 parents = [[('rev', p.rev()), ('node', p.hex())]
823 parents = [[('rev', p.rev()), ('node', p.hex())]
882 for p in self._meaningful_parentrevs(ctx)]
824 for p in self._meaningful_parentrevs(ctx)]
883 return showlist('parent', parents, **args)
825 return showlist(templ, 'parent', parents, **args)
884
826
885 def showtags(**args):
827 def showtags(templ, **args):
886 return showlist('tag', ctx.tags(), **args)
828 return showlist(templ, 'tag', ctx.tags(), **args)
887
829
888 def showextras(**args):
830 def showextras(templ, **args):
889 for key, value in sorted(ctx.extra().items()):
831 for key, value in sorted(ctx.extra().items()):
890 args = args.copy()
832 args = args.copy()
891 args.update(dict(key=key, value=value))
833 args.update(dict(key=key, value=value))
892 yield self.t('extra', **args)
834 yield templ('extra', **args)
893
835
894 def showcopies(**args):
836 def showcopies(templ, **args):
895 c = [{'name': x[0], 'source': x[1]} for x in copies]
837 c = [{'name': x[0], 'source': x[1]} for x in copies]
896 return showlist('file_copy', c, plural='file_copies', **args)
838 return showlist(templ, 'file_copy', c, plural='file_copies', **args)
897
839
898 files = []
840 files = []
899 def getfiles():
841 def getfiles():
900 if not files:
842 if not files:
901 files[:] = self.repo.status(ctx.parents()[0].node(),
843 files[:] = self.repo.status(ctx.parents()[0].node(),
902 ctx.node())[:3]
844 ctx.node())[:3]
903 return files
845 return files
904 def showfiles(**args):
846 def showfiles(templ, **args):
905 return showlist('file', ctx.files(), **args)
847 return showlist(templ, 'file', ctx.files(), **args)
906 def showmods(**args):
848 def showmods(templ, **args):
907 return showlist('file_mod', getfiles()[0], **args)
849 return showlist(templ, 'file_mod', getfiles()[0], **args)
908 def showadds(**args):
850 def showadds(templ, **args):
909 return showlist('file_add', getfiles()[1], **args)
851 return showlist(templ, 'file_add', getfiles()[1], **args)
910 def showdels(**args):
852 def showdels(templ, **args):
911 return showlist('file_del', getfiles()[2], **args)
853 return showlist(templ, 'file_del', getfiles()[2], **args)
912 def showmanifest(**args):
854 def showmanifest(templ, **args):
913 args = args.copy()
855 args = args.copy()
914 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
856 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
915 node=hex(ctx.changeset()[0])))
857 node=hex(ctx.changeset()[0])))
916 return self.t('manifest', **args)
858 return templ('manifest', **args)
917
859
918 def showdiffstat(**args):
860 def showdiffstat(templ, **args):
919 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
861 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
920 files, adds, removes = 0, 0, 0
862 files, adds, removes = 0, 0, 0
921 for i in patch.diffstatdata(util.iterlines(diff)):
863 for i in patch.diffstatdata(util.iterlines(diff)):
922 files += 1
864 files += 1
923 adds += i[1]
865 adds += i[1]
924 removes += i[2]
866 removes += i[2]
925 return '%s: +%s/-%s' % (files, adds, removes)
867 return '%s: +%s/-%s' % (files, adds, removes)
926
868
927 def showlatesttag(**args):
869 def showlatesttag(templ, **args):
928 return self._latesttaginfo(ctx.rev())[2]
870 return self._latesttaginfo(ctx.rev())[2]
929 def showlatesttagdistance(**args):
871 def showlatesttagdistance(templ, **args):
930 return self._latesttaginfo(ctx.rev())[1]
872 return self._latesttaginfo(ctx.rev())[1]
931
873
932 defprops = {
874 defprops = {
933 'author': ctx.user(),
875 'author': ctx.user(),
934 'branches': showbranches,
876 'branches': showbranches,
935 'date': ctx.date(),
877 'date': ctx.date(),
936 'desc': ctx.description().strip(),
878 'desc': ctx.description().strip(),
937 'file_adds': showadds,
879 'file_adds': showadds,
938 'file_dels': showdels,
880 'file_dels': showdels,
939 'file_mods': showmods,
881 'file_mods': showmods,
940 'files': showfiles,
882 'files': showfiles,
941 'file_copies': showcopies,
883 'file_copies': showcopies,
942 'manifest': showmanifest,
884 'manifest': showmanifest,
943 'node': ctx.hex(),
885 'node': ctx.hex(),
944 'parents': showparents,
886 'parents': showparents,
945 'rev': ctx.rev(),
887 'rev': ctx.rev(),
946 'tags': showtags,
888 'tags': showtags,
947 'extras': showextras,
889 'extras': showextras,
948 'diffstat': showdiffstat,
890 'diffstat': showdiffstat,
949 'latesttag': showlatesttag,
891 'latesttag': showlatesttag,
950 'latesttagdistance': showlatesttagdistance,
892 'latesttagdistance': showlatesttagdistance,
951 }
893 }
952 props = props.copy()
894 props = props.copy()
953 props.update(defprops)
895 props.update(defprops)
896 props['templ'] = self.t
954
897
955 # find correct templates for current mode
898 # find correct templates for current mode
956
899
957 tmplmodes = [
900 tmplmodes = [
958 (True, None),
901 (True, None),
959 (self.ui.verbose, 'verbose'),
902 (self.ui.verbose, 'verbose'),
960 (self.ui.quiet, 'quiet'),
903 (self.ui.quiet, 'quiet'),
961 (self.ui.debugflag, 'debug'),
904 (self.ui.debugflag, 'debug'),
962 ]
905 ]
963
906
964 types = {'header': '', 'changeset': 'changeset'}
907 types = {'header': '', 'changeset': 'changeset'}
965 for mode, postfix in tmplmodes:
908 for mode, postfix in tmplmodes:
966 for type in types:
909 for type in types:
967 cur = postfix and ('%s_%s' % (type, postfix)) or type
910 cur = postfix and ('%s_%s' % (type, postfix)) or type
968 if mode and cur in self.t:
911 if mode and cur in self.t:
969 types[type] = cur
912 types[type] = cur
970
913
971 try:
914 try:
972
915
973 # write header
916 # write header
974 if types['header']:
917 if types['header']:
975 h = templater.stringify(self.t(types['header'], **props))
918 h = templater.stringify(self.t(types['header'], **props))
976 if self.buffered:
919 if self.buffered:
977 self.header[ctx.rev()] = h
920 self.header[ctx.rev()] = h
978 else:
921 else:
979 self.ui.write(h)
922 self.ui.write(h)
980
923
981 # write changeset metadata, then patch if requested
924 # write changeset metadata, then patch if requested
982 key = types['changeset']
925 key = types['changeset']
983 self.ui.write(templater.stringify(self.t(key, **props)))
926 self.ui.write(templater.stringify(self.t(key, **props)))
984 self.showpatch(ctx.node())
927 self.showpatch(ctx.node())
985
928
986 except KeyError, inst:
929 except KeyError, inst:
987 msg = _("%s: no key named '%s'")
930 msg = _("%s: no key named '%s'")
988 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
931 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
989 except SyntaxError, inst:
932 except SyntaxError, inst:
990 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
933 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
991
934
992 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
935 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
993 """show one changeset using template or regular display.
936 """show one changeset using template or regular display.
994
937
995 Display format will be the first non-empty hit of:
938 Display format will be the first non-empty hit of:
996 1. option 'template'
939 1. option 'template'
997 2. option 'style'
940 2. option 'style'
998 3. [ui] setting 'logtemplate'
941 3. [ui] setting 'logtemplate'
999 4. [ui] setting 'style'
942 4. [ui] setting 'style'
1000 If all of these values are either the unset or the empty string,
943 If all of these values are either the unset or the empty string,
1001 regular display via changeset_printer() is done.
944 regular display via changeset_printer() is done.
1002 """
945 """
1003 # options
946 # options
1004 patch = False
947 patch = False
1005 if opts.get('patch'):
948 if opts.get('patch'):
1006 patch = matchfn or matchall(repo)
949 patch = matchfn or matchall(repo)
1007
950
1008 tmpl = opts.get('template')
951 tmpl = opts.get('template')
1009 style = None
952 style = None
1010 if tmpl:
953 if tmpl:
1011 tmpl = templater.parsestring(tmpl, quoted=False)
954 tmpl = templater.parsestring(tmpl, quoted=False)
1012 else:
955 else:
1013 style = opts.get('style')
956 style = opts.get('style')
1014
957
1015 # ui settings
958 # ui settings
1016 if not (tmpl or style):
959 if not (tmpl or style):
1017 tmpl = ui.config('ui', 'logtemplate')
960 tmpl = ui.config('ui', 'logtemplate')
1018 if tmpl:
961 if tmpl:
1019 tmpl = templater.parsestring(tmpl)
962 tmpl = templater.parsestring(tmpl)
1020 else:
963 else:
1021 style = ui.config('ui', 'style')
964 style = ui.config('ui', 'style')
1022
965
1023 if not (tmpl or style):
966 if not (tmpl or style):
1024 return changeset_printer(ui, repo, patch, opts, buffered)
967 return changeset_printer(ui, repo, patch, opts, buffered)
1025
968
1026 mapfile = None
969 mapfile = None
1027 if style and not tmpl:
970 if style and not tmpl:
1028 mapfile = style
971 mapfile = style
1029 if not os.path.split(mapfile)[0]:
972 if not os.path.split(mapfile)[0]:
1030 mapname = (templater.templatepath('map-cmdline.' + mapfile)
973 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1031 or templater.templatepath(mapfile))
974 or templater.templatepath(mapfile))
1032 if mapname: mapfile = mapname
975 if mapname: mapfile = mapname
1033
976
1034 try:
977 try:
1035 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
978 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1036 except SyntaxError, inst:
979 except SyntaxError, inst:
1037 raise util.Abort(inst.args[0])
980 raise util.Abort(inst.args[0])
1038 if tmpl: t.use_template(tmpl)
981 if tmpl: t.use_template(tmpl)
1039 return t
982 return t
1040
983
1041 def finddate(ui, repo, date):
984 def finddate(ui, repo, date):
1042 """Find the tipmost changeset that matches the given date spec"""
985 """Find the tipmost changeset that matches the given date spec"""
1043
986
1044 df = util.matchdate(date)
987 df = util.matchdate(date)
1045 m = matchall(repo)
988 m = matchall(repo)
1046 results = {}
989 results = {}
1047
990
1048 def prep(ctx, fns):
991 def prep(ctx, fns):
1049 d = ctx.date()
992 d = ctx.date()
1050 if df(d[0]):
993 if df(d[0]):
1051 results[ctx.rev()] = d
994 results[ctx.rev()] = d
1052
995
1053 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
996 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1054 rev = ctx.rev()
997 rev = ctx.rev()
1055 if rev in results:
998 if rev in results:
1056 ui.status(_("Found revision %s from %s\n") %
999 ui.status(_("Found revision %s from %s\n") %
1057 (rev, util.datestr(results[rev])))
1000 (rev, util.datestr(results[rev])))
1058 return str(rev)
1001 return str(rev)
1059
1002
1060 raise util.Abort(_("revision matching date not found"))
1003 raise util.Abort(_("revision matching date not found"))
1061
1004
1062 def walkchangerevs(repo, match, opts, prepare):
1005 def walkchangerevs(repo, match, opts, prepare):
1063 '''Iterate over files and the revs in which they changed.
1006 '''Iterate over files and the revs in which they changed.
1064
1007
1065 Callers most commonly need to iterate backwards over the history
1008 Callers most commonly need to iterate backwards over the history
1066 in which they are interested. Doing so has awful (quadratic-looking)
1009 in which they are interested. Doing so has awful (quadratic-looking)
1067 performance, so we use iterators in a "windowed" way.
1010 performance, so we use iterators in a "windowed" way.
1068
1011
1069 We walk a window of revisions in the desired order. Within the
1012 We walk a window of revisions in the desired order. Within the
1070 window, we first walk forwards to gather data, then in the desired
1013 window, we first walk forwards to gather data, then in the desired
1071 order (usually backwards) to display it.
1014 order (usually backwards) to display it.
1072
1015
1073 This function returns an iterator yielding contexts. Before
1016 This function returns an iterator yielding contexts. Before
1074 yielding each context, the iterator will first call the prepare
1017 yielding each context, the iterator will first call the prepare
1075 function on each context in the window in forward order.'''
1018 function on each context in the window in forward order.'''
1076
1019
1077 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1020 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1078 if start < end:
1021 if start < end:
1079 while start < end:
1022 while start < end:
1080 yield start, min(windowsize, end-start)
1023 yield start, min(windowsize, end-start)
1081 start += windowsize
1024 start += windowsize
1082 if windowsize < sizelimit:
1025 if windowsize < sizelimit:
1083 windowsize *= 2
1026 windowsize *= 2
1084 else:
1027 else:
1085 while start > end:
1028 while start > end:
1086 yield start, min(windowsize, start-end-1)
1029 yield start, min(windowsize, start-end-1)
1087 start -= windowsize
1030 start -= windowsize
1088 if windowsize < sizelimit:
1031 if windowsize < sizelimit:
1089 windowsize *= 2
1032 windowsize *= 2
1090
1033
1091 follow = opts.get('follow') or opts.get('follow_first')
1034 follow = opts.get('follow') or opts.get('follow_first')
1092
1035
1093 if not len(repo):
1036 if not len(repo):
1094 return []
1037 return []
1095
1038
1096 if follow:
1039 if follow:
1097 defrange = '%s:0' % repo['.'].rev()
1040 defrange = '%s:0' % repo['.'].rev()
1098 else:
1041 else:
1099 defrange = '-1:0'
1042 defrange = '-1:0'
1100 revs = revrange(repo, opts['rev'] or [defrange])
1043 revs = revrange(repo, opts['rev'] or [defrange])
1101 wanted = set()
1044 wanted = set()
1102 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1045 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1103 fncache = {}
1046 fncache = {}
1104 change = util.cachefunc(repo.changectx)
1047 change = util.cachefunc(repo.changectx)
1105
1048
1106 if not slowpath and not match.files():
1049 if not slowpath and not match.files():
1107 # No files, no patterns. Display all revs.
1050 # No files, no patterns. Display all revs.
1108 wanted = set(revs)
1051 wanted = set(revs)
1109 copies = []
1052 copies = []
1110
1053
1111 if not slowpath:
1054 if not slowpath:
1112 # Only files, no patterns. Check the history of each file.
1055 # Only files, no patterns. Check the history of each file.
1113 def filerevgen(filelog, node):
1056 def filerevgen(filelog, node):
1114 cl_count = len(repo)
1057 cl_count = len(repo)
1115 if node is None:
1058 if node is None:
1116 last = len(filelog) - 1
1059 last = len(filelog) - 1
1117 else:
1060 else:
1118 last = filelog.rev(node)
1061 last = filelog.rev(node)
1119 for i, window in increasing_windows(last, nullrev):
1062 for i, window in increasing_windows(last, nullrev):
1120 revs = []
1063 revs = []
1121 for j in xrange(i - window, i + 1):
1064 for j in xrange(i - window, i + 1):
1122 n = filelog.node(j)
1065 n = filelog.node(j)
1123 revs.append((filelog.linkrev(j),
1066 revs.append((filelog.linkrev(j),
1124 follow and filelog.renamed(n)))
1067 follow and filelog.renamed(n)))
1125 for rev in reversed(revs):
1068 for rev in reversed(revs):
1126 # only yield rev for which we have the changelog, it can
1069 # only yield rev for which we have the changelog, it can
1127 # happen while doing "hg log" during a pull or commit
1070 # happen while doing "hg log" during a pull or commit
1128 if rev[0] < cl_count:
1071 if rev[0] < cl_count:
1129 yield rev
1072 yield rev
1130 def iterfiles():
1073 def iterfiles():
1131 for filename in match.files():
1074 for filename in match.files():
1132 yield filename, None
1075 yield filename, None
1133 for filename_node in copies:
1076 for filename_node in copies:
1134 yield filename_node
1077 yield filename_node
1135 minrev, maxrev = min(revs), max(revs)
1078 minrev, maxrev = min(revs), max(revs)
1136 for file_, node in iterfiles():
1079 for file_, node in iterfiles():
1137 filelog = repo.file(file_)
1080 filelog = repo.file(file_)
1138 if not len(filelog):
1081 if not len(filelog):
1139 if node is None:
1082 if node is None:
1140 # A zero count may be a directory or deleted file, so
1083 # A zero count may be a directory or deleted file, so
1141 # try to find matching entries on the slow path.
1084 # try to find matching entries on the slow path.
1142 if follow:
1085 if follow:
1143 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1086 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1144 slowpath = True
1087 slowpath = True
1145 break
1088 break
1146 else:
1089 else:
1147 continue
1090 continue
1148 for rev, copied in filerevgen(filelog, node):
1091 for rev, copied in filerevgen(filelog, node):
1149 if rev <= maxrev:
1092 if rev <= maxrev:
1150 if rev < minrev:
1093 if rev < minrev:
1151 break
1094 break
1152 fncache.setdefault(rev, [])
1095 fncache.setdefault(rev, [])
1153 fncache[rev].append(file_)
1096 fncache[rev].append(file_)
1154 wanted.add(rev)
1097 wanted.add(rev)
1155 if follow and copied:
1098 if follow and copied:
1156 copies.append(copied)
1099 copies.append(copied)
1157 if slowpath:
1100 if slowpath:
1158 if follow:
1101 if follow:
1159 raise util.Abort(_('can only follow copies/renames for explicit '
1102 raise util.Abort(_('can only follow copies/renames for explicit '
1160 'filenames'))
1103 'filenames'))
1161
1104
1162 # The slow path checks files modified in every changeset.
1105 # The slow path checks files modified in every changeset.
1163 def changerevgen():
1106 def changerevgen():
1164 for i, window in increasing_windows(len(repo) - 1, nullrev):
1107 for i, window in increasing_windows(len(repo) - 1, nullrev):
1165 for j in xrange(i - window, i + 1):
1108 for j in xrange(i - window, i + 1):
1166 yield change(j)
1109 yield change(j)
1167
1110
1168 for ctx in changerevgen():
1111 for ctx in changerevgen():
1169 matches = filter(match, ctx.files())
1112 matches = filter(match, ctx.files())
1170 if matches:
1113 if matches:
1171 fncache[ctx.rev()] = matches
1114 fncache[ctx.rev()] = matches
1172 wanted.add(ctx.rev())
1115 wanted.add(ctx.rev())
1173
1116
1174 class followfilter(object):
1117 class followfilter(object):
1175 def __init__(self, onlyfirst=False):
1118 def __init__(self, onlyfirst=False):
1176 self.startrev = nullrev
1119 self.startrev = nullrev
1177 self.roots = set()
1120 self.roots = set()
1178 self.onlyfirst = onlyfirst
1121 self.onlyfirst = onlyfirst
1179
1122
1180 def match(self, rev):
1123 def match(self, rev):
1181 def realparents(rev):
1124 def realparents(rev):
1182 if self.onlyfirst:
1125 if self.onlyfirst:
1183 return repo.changelog.parentrevs(rev)[0:1]
1126 return repo.changelog.parentrevs(rev)[0:1]
1184 else:
1127 else:
1185 return filter(lambda x: x != nullrev,
1128 return filter(lambda x: x != nullrev,
1186 repo.changelog.parentrevs(rev))
1129 repo.changelog.parentrevs(rev))
1187
1130
1188 if self.startrev == nullrev:
1131 if self.startrev == nullrev:
1189 self.startrev = rev
1132 self.startrev = rev
1190 return True
1133 return True
1191
1134
1192 if rev > self.startrev:
1135 if rev > self.startrev:
1193 # forward: all descendants
1136 # forward: all descendants
1194 if not self.roots:
1137 if not self.roots:
1195 self.roots.add(self.startrev)
1138 self.roots.add(self.startrev)
1196 for parent in realparents(rev):
1139 for parent in realparents(rev):
1197 if parent in self.roots:
1140 if parent in self.roots:
1198 self.roots.add(rev)
1141 self.roots.add(rev)
1199 return True
1142 return True
1200 else:
1143 else:
1201 # backwards: all parents
1144 # backwards: all parents
1202 if not self.roots:
1145 if not self.roots:
1203 self.roots.update(realparents(self.startrev))
1146 self.roots.update(realparents(self.startrev))
1204 if rev in self.roots:
1147 if rev in self.roots:
1205 self.roots.remove(rev)
1148 self.roots.remove(rev)
1206 self.roots.update(realparents(rev))
1149 self.roots.update(realparents(rev))
1207 return True
1150 return True
1208
1151
1209 return False
1152 return False
1210
1153
1211 # it might be worthwhile to do this in the iterator if the rev range
1154 # it might be worthwhile to do this in the iterator if the rev range
1212 # is descending and the prune args are all within that range
1155 # is descending and the prune args are all within that range
1213 for rev in opts.get('prune', ()):
1156 for rev in opts.get('prune', ()):
1214 rev = repo.changelog.rev(repo.lookup(rev))
1157 rev = repo.changelog.rev(repo.lookup(rev))
1215 ff = followfilter()
1158 ff = followfilter()
1216 stop = min(revs[0], revs[-1])
1159 stop = min(revs[0], revs[-1])
1217 for x in xrange(rev, stop-1, -1):
1160 for x in xrange(rev, stop-1, -1):
1218 if ff.match(x):
1161 if ff.match(x):
1219 wanted.discard(x)
1162 wanted.discard(x)
1220
1163
1221 def iterate():
1164 def iterate():
1222 if follow and not match.files():
1165 if follow and not match.files():
1223 ff = followfilter(onlyfirst=opts.get('follow_first'))
1166 ff = followfilter(onlyfirst=opts.get('follow_first'))
1224 def want(rev):
1167 def want(rev):
1225 return ff.match(rev) and rev in wanted
1168 return ff.match(rev) and rev in wanted
1226 else:
1169 else:
1227 def want(rev):
1170 def want(rev):
1228 return rev in wanted
1171 return rev in wanted
1229
1172
1230 for i, window in increasing_windows(0, len(revs)):
1173 for i, window in increasing_windows(0, len(revs)):
1231 change = util.cachefunc(repo.changectx)
1174 change = util.cachefunc(repo.changectx)
1232 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1175 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1233 for rev in sorted(nrevs):
1176 for rev in sorted(nrevs):
1234 fns = fncache.get(rev)
1177 fns = fncache.get(rev)
1235 ctx = change(rev)
1178 ctx = change(rev)
1236 if not fns:
1179 if not fns:
1237 def fns_generator():
1180 def fns_generator():
1238 for f in ctx.files():
1181 for f in ctx.files():
1239 if match(f):
1182 if match(f):
1240 yield f
1183 yield f
1241 fns = fns_generator()
1184 fns = fns_generator()
1242 prepare(ctx, fns)
1185 prepare(ctx, fns)
1243 for rev in nrevs:
1186 for rev in nrevs:
1244 yield change(rev)
1187 yield change(rev)
1245 return iterate()
1188 return iterate()
1246
1189
1247 def commit(ui, repo, commitfunc, pats, opts):
1190 def commit(ui, repo, commitfunc, pats, opts):
1248 '''commit the specified files or all outstanding changes'''
1191 '''commit the specified files or all outstanding changes'''
1249 date = opts.get('date')
1192 date = opts.get('date')
1250 if date:
1193 if date:
1251 opts['date'] = util.parsedate(date)
1194 opts['date'] = util.parsedate(date)
1252 message = logmessage(opts)
1195 message = logmessage(opts)
1253
1196
1254 # extract addremove carefully -- this function can be called from a command
1197 # extract addremove carefully -- this function can be called from a command
1255 # that doesn't support addremove
1198 # that doesn't support addremove
1256 if opts.get('addremove'):
1199 if opts.get('addremove'):
1257 addremove(repo, pats, opts)
1200 addremove(repo, pats, opts)
1258
1201
1259 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1202 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1260
1203
1261 def commiteditor(repo, ctx, subs):
1204 def commiteditor(repo, ctx, subs):
1262 if ctx.description():
1205 if ctx.description():
1263 return ctx.description()
1206 return ctx.description()
1264 return commitforceeditor(repo, ctx, subs)
1207 return commitforceeditor(repo, ctx, subs)
1265
1208
1266 def commitforceeditor(repo, ctx, subs):
1209 def commitforceeditor(repo, ctx, subs):
1267 edittext = []
1210 edittext = []
1268 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1211 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1269 if ctx.description():
1212 if ctx.description():
1270 edittext.append(ctx.description())
1213 edittext.append(ctx.description())
1271 edittext.append("")
1214 edittext.append("")
1272 edittext.append("") # Empty line between message and comments.
1215 edittext.append("") # Empty line between message and comments.
1273 edittext.append(_("HG: Enter commit message."
1216 edittext.append(_("HG: Enter commit message."
1274 " Lines beginning with 'HG:' are removed."))
1217 " Lines beginning with 'HG:' are removed."))
1275 edittext.append(_("HG: Leave message empty to abort commit."))
1218 edittext.append(_("HG: Leave message empty to abort commit."))
1276 edittext.append("HG: --")
1219 edittext.append("HG: --")
1277 edittext.append(_("HG: user: %s") % ctx.user())
1220 edittext.append(_("HG: user: %s") % ctx.user())
1278 if ctx.p2():
1221 if ctx.p2():
1279 edittext.append(_("HG: branch merge"))
1222 edittext.append(_("HG: branch merge"))
1280 if ctx.branch():
1223 if ctx.branch():
1281 edittext.append(_("HG: branch '%s'")
1224 edittext.append(_("HG: branch '%s'")
1282 % encoding.tolocal(ctx.branch()))
1225 % encoding.tolocal(ctx.branch()))
1283 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1226 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1284 edittext.extend([_("HG: added %s") % f for f in added])
1227 edittext.extend([_("HG: added %s") % f for f in added])
1285 edittext.extend([_("HG: changed %s") % f for f in modified])
1228 edittext.extend([_("HG: changed %s") % f for f in modified])
1286 edittext.extend([_("HG: removed %s") % f for f in removed])
1229 edittext.extend([_("HG: removed %s") % f for f in removed])
1287 if not added and not modified and not removed:
1230 if not added and not modified and not removed:
1288 edittext.append(_("HG: no files changed"))
1231 edittext.append(_("HG: no files changed"))
1289 edittext.append("")
1232 edittext.append("")
1290 # run editor in the repository root
1233 # run editor in the repository root
1291 olddir = os.getcwd()
1234 olddir = os.getcwd()
1292 os.chdir(repo.root)
1235 os.chdir(repo.root)
1293 text = repo.ui.edit("\n".join(edittext), ctx.user())
1236 text = repo.ui.edit("\n".join(edittext), ctx.user())
1294 text = re.sub("(?m)^HG:.*\n", "", text)
1237 text = re.sub("(?m)^HG:.*\n", "", text)
1295 os.chdir(olddir)
1238 os.chdir(olddir)
1296
1239
1297 if not text.strip():
1240 if not text.strip():
1298 raise util.Abort(_("empty commit message"))
1241 raise util.Abort(_("empty commit message"))
1299
1242
1300 return text
1243 return text
General Comments 0
You need to be logged in to leave comments. Login now