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