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