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