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