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