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