##// END OF EJS Templates
cmdutil.logmessage: options should be optional
Alexander Solovyov -
r7667:bd5c37d7 default
parent child Browse files
Show More
@@ -1,1186 +1,1186 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['message']
71 message = opts.get('message')
72 logfile = opts['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, buffered):
572 def __init__(self, ui, repo, patch, 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.header = {}
577 self.header = {}
578 self.hunk = {}
578 self.hunk = {}
579 self.lastheader = None
579 self.lastheader = None
580
580
581 def flush(self, rev):
581 def flush(self, rev):
582 if rev in self.header:
582 if rev in self.header:
583 h = self.header[rev]
583 h = self.header[rev]
584 if h != self.lastheader:
584 if h != self.lastheader:
585 self.lastheader = h
585 self.lastheader = h
586 self.ui.write(h)
586 self.ui.write(h)
587 del self.header[rev]
587 del self.header[rev]
588 if rev in self.hunk:
588 if rev in self.hunk:
589 self.ui.write(self.hunk[rev])
589 self.ui.write(self.hunk[rev])
590 del self.hunk[rev]
590 del self.hunk[rev]
591 return 1
591 return 1
592 return 0
592 return 0
593
593
594 def show(self, ctx, copies=(), **props):
594 def show(self, ctx, copies=(), **props):
595 if self.buffered:
595 if self.buffered:
596 self.ui.pushbuffer()
596 self.ui.pushbuffer()
597 self._show(ctx, copies, props)
597 self._show(ctx, copies, props)
598 self.hunk[ctx.rev()] = self.ui.popbuffer()
598 self.hunk[ctx.rev()] = self.ui.popbuffer()
599 else:
599 else:
600 self._show(ctx, copies, props)
600 self._show(ctx, copies, props)
601
601
602 def _show(self, ctx, copies, props):
602 def _show(self, ctx, copies, props):
603 '''show a single changeset or file revision'''
603 '''show a single changeset or file revision'''
604 changenode = ctx.node()
604 changenode = ctx.node()
605 rev = ctx.rev()
605 rev = ctx.rev()
606
606
607 if self.ui.quiet:
607 if self.ui.quiet:
608 self.ui.write("%d:%s\n" % (rev, short(changenode)))
608 self.ui.write("%d:%s\n" % (rev, short(changenode)))
609 return
609 return
610
610
611 log = self.repo.changelog
611 log = self.repo.changelog
612 changes = log.read(changenode)
612 changes = log.read(changenode)
613 date = util.datestr(changes[2])
613 date = util.datestr(changes[2])
614 extra = changes[5]
614 extra = changes[5]
615 branch = extra.get("branch")
615 branch = extra.get("branch")
616
616
617 hexfunc = self.ui.debugflag and hex or short
617 hexfunc = self.ui.debugflag and hex or short
618
618
619 parents = [(p, hexfunc(log.node(p)))
619 parents = [(p, hexfunc(log.node(p)))
620 for p in self._meaningful_parentrevs(log, rev)]
620 for p in self._meaningful_parentrevs(log, rev)]
621
621
622 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
622 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
623
623
624 # don't show the default branch name
624 # don't show the default branch name
625 if branch != 'default':
625 if branch != 'default':
626 branch = util.tolocal(branch)
626 branch = util.tolocal(branch)
627 self.ui.write(_("branch: %s\n") % branch)
627 self.ui.write(_("branch: %s\n") % branch)
628 for tag in self.repo.nodetags(changenode):
628 for tag in self.repo.nodetags(changenode):
629 self.ui.write(_("tag: %s\n") % tag)
629 self.ui.write(_("tag: %s\n") % tag)
630 for parent in parents:
630 for parent in parents:
631 self.ui.write(_("parent: %d:%s\n") % parent)
631 self.ui.write(_("parent: %d:%s\n") % parent)
632
632
633 if self.ui.debugflag:
633 if self.ui.debugflag:
634 self.ui.write(_("manifest: %d:%s\n") %
634 self.ui.write(_("manifest: %d:%s\n") %
635 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
635 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
636 self.ui.write(_("user: %s\n") % changes[1])
636 self.ui.write(_("user: %s\n") % changes[1])
637 self.ui.write(_("date: %s\n") % date)
637 self.ui.write(_("date: %s\n") % date)
638
638
639 if self.ui.debugflag:
639 if self.ui.debugflag:
640 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
640 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
641 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
641 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
642 files):
642 files):
643 if value:
643 if value:
644 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
644 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
645 elif changes[3] and self.ui.verbose:
645 elif changes[3] and self.ui.verbose:
646 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
646 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
647 if copies and self.ui.verbose:
647 if copies and self.ui.verbose:
648 copies = ['%s (%s)' % c for c in copies]
648 copies = ['%s (%s)' % c for c in copies]
649 self.ui.write(_("copies: %s\n") % ' '.join(copies))
649 self.ui.write(_("copies: %s\n") % ' '.join(copies))
650
650
651 if extra and self.ui.debugflag:
651 if extra and self.ui.debugflag:
652 for key, value in util.sort(extra.items()):
652 for key, value in util.sort(extra.items()):
653 self.ui.write(_("extra: %s=%s\n")
653 self.ui.write(_("extra: %s=%s\n")
654 % (key, value.encode('string_escape')))
654 % (key, value.encode('string_escape')))
655
655
656 description = changes[4].strip()
656 description = changes[4].strip()
657 if description:
657 if description:
658 if self.ui.verbose:
658 if self.ui.verbose:
659 self.ui.write(_("description:\n"))
659 self.ui.write(_("description:\n"))
660 self.ui.write(description)
660 self.ui.write(description)
661 self.ui.write("\n\n")
661 self.ui.write("\n\n")
662 else:
662 else:
663 self.ui.write(_("summary: %s\n") %
663 self.ui.write(_("summary: %s\n") %
664 description.splitlines()[0])
664 description.splitlines()[0])
665 self.ui.write("\n")
665 self.ui.write("\n")
666
666
667 self.showpatch(changenode)
667 self.showpatch(changenode)
668
668
669 def showpatch(self, node):
669 def showpatch(self, node):
670 if self.patch:
670 if self.patch:
671 prev = self.repo.changelog.parents(node)[0]
671 prev = self.repo.changelog.parents(node)[0]
672 chunks = patch.diff(self.repo, prev, node, match=self.patch,
672 chunks = patch.diff(self.repo, prev, node, match=self.patch,
673 opts=patch.diffopts(self.ui))
673 opts=patch.diffopts(self.ui))
674 for chunk in chunks:
674 for chunk in chunks:
675 self.ui.write(chunk)
675 self.ui.write(chunk)
676 self.ui.write("\n")
676 self.ui.write("\n")
677
677
678 def _meaningful_parentrevs(self, log, rev):
678 def _meaningful_parentrevs(self, log, rev):
679 """Return list of meaningful (or all if debug) parentrevs for rev.
679 """Return list of meaningful (or all if debug) parentrevs for rev.
680
680
681 For merges (two non-nullrev revisions) both parents are meaningful.
681 For merges (two non-nullrev revisions) both parents are meaningful.
682 Otherwise the first parent revision is considered meaningful if it
682 Otherwise the first parent revision is considered meaningful if it
683 is not the preceding revision.
683 is not the preceding revision.
684 """
684 """
685 parents = log.parentrevs(rev)
685 parents = log.parentrevs(rev)
686 if not self.ui.debugflag and parents[1] == nullrev:
686 if not self.ui.debugflag and parents[1] == nullrev:
687 if parents[0] >= rev - 1:
687 if parents[0] >= rev - 1:
688 parents = []
688 parents = []
689 else:
689 else:
690 parents = [parents[0]]
690 parents = [parents[0]]
691 return parents
691 return parents
692
692
693
693
694 class changeset_templater(changeset_printer):
694 class changeset_templater(changeset_printer):
695 '''format changeset information.'''
695 '''format changeset information.'''
696
696
697 def __init__(self, ui, repo, patch, mapfile, buffered):
697 def __init__(self, ui, repo, patch, mapfile, buffered):
698 changeset_printer.__init__(self, ui, repo, patch, buffered)
698 changeset_printer.__init__(self, ui, repo, patch, buffered)
699 filters = templatefilters.filters.copy()
699 filters = templatefilters.filters.copy()
700 filters['formatnode'] = (ui.debugflag and (lambda x: x)
700 filters['formatnode'] = (ui.debugflag and (lambda x: x)
701 or (lambda x: x[:12]))
701 or (lambda x: x[:12]))
702 self.t = templater.templater(mapfile, filters,
702 self.t = templater.templater(mapfile, filters,
703 cache={
703 cache={
704 'parent': '{rev}:{node|formatnode} ',
704 'parent': '{rev}:{node|formatnode} ',
705 'manifest': '{rev}:{node|formatnode}',
705 'manifest': '{rev}:{node|formatnode}',
706 'filecopy': '{name} ({source})'})
706 'filecopy': '{name} ({source})'})
707
707
708 def use_template(self, t):
708 def use_template(self, t):
709 '''set template string to use'''
709 '''set template string to use'''
710 self.t.cache['changeset'] = t
710 self.t.cache['changeset'] = t
711
711
712 def _show(self, ctx, copies, props):
712 def _show(self, ctx, copies, props):
713 '''show a single changeset or file revision'''
713 '''show a single changeset or file revision'''
714 changenode = ctx.node()
714 changenode = ctx.node()
715 rev = ctx.rev()
715 rev = ctx.rev()
716
716
717 log = self.repo.changelog
717 log = self.repo.changelog
718 changes = log.read(changenode)
718 changes = log.read(changenode)
719
719
720 def showlist(name, values, plural=None, **args):
720 def showlist(name, values, plural=None, **args):
721 '''expand set of values.
721 '''expand set of values.
722 name is name of key in template map.
722 name is name of key in template map.
723 values is list of strings or dicts.
723 values is list of strings or dicts.
724 plural is plural of name, if not simply name + 's'.
724 plural is plural of name, if not simply name + 's'.
725
725
726 expansion works like this, given name 'foo'.
726 expansion works like this, given name 'foo'.
727
727
728 if values is empty, expand 'no_foos'.
728 if values is empty, expand 'no_foos'.
729
729
730 if 'foo' not in template map, return values as a string,
730 if 'foo' not in template map, return values as a string,
731 joined by space.
731 joined by space.
732
732
733 expand 'start_foos'.
733 expand 'start_foos'.
734
734
735 for each value, expand 'foo'. if 'last_foo' in template
735 for each value, expand 'foo'. if 'last_foo' in template
736 map, expand it instead of 'foo' for last key.
736 map, expand it instead of 'foo' for last key.
737
737
738 expand 'end_foos'.
738 expand 'end_foos'.
739 '''
739 '''
740 if plural: names = plural
740 if plural: names = plural
741 else: names = name + 's'
741 else: names = name + 's'
742 if not values:
742 if not values:
743 noname = 'no_' + names
743 noname = 'no_' + names
744 if noname in self.t:
744 if noname in self.t:
745 yield self.t(noname, **args)
745 yield self.t(noname, **args)
746 return
746 return
747 if name not in self.t:
747 if name not in self.t:
748 if isinstance(values[0], str):
748 if isinstance(values[0], str):
749 yield ' '.join(values)
749 yield ' '.join(values)
750 else:
750 else:
751 for v in values:
751 for v in values:
752 yield dict(v, **args)
752 yield dict(v, **args)
753 return
753 return
754 startname = 'start_' + names
754 startname = 'start_' + names
755 if startname in self.t:
755 if startname in self.t:
756 yield self.t(startname, **args)
756 yield self.t(startname, **args)
757 vargs = args.copy()
757 vargs = args.copy()
758 def one(v, tag=name):
758 def one(v, tag=name):
759 try:
759 try:
760 vargs.update(v)
760 vargs.update(v)
761 except (AttributeError, ValueError):
761 except (AttributeError, ValueError):
762 try:
762 try:
763 for a, b in v:
763 for a, b in v:
764 vargs[a] = b
764 vargs[a] = b
765 except ValueError:
765 except ValueError:
766 vargs[name] = v
766 vargs[name] = v
767 return self.t(tag, **vargs)
767 return self.t(tag, **vargs)
768 lastname = 'last_' + name
768 lastname = 'last_' + name
769 if lastname in self.t:
769 if lastname in self.t:
770 last = values.pop()
770 last = values.pop()
771 else:
771 else:
772 last = None
772 last = None
773 for v in values:
773 for v in values:
774 yield one(v)
774 yield one(v)
775 if last is not None:
775 if last is not None:
776 yield one(last, tag=lastname)
776 yield one(last, tag=lastname)
777 endname = 'end_' + names
777 endname = 'end_' + names
778 if endname in self.t:
778 if endname in self.t:
779 yield self.t(endname, **args)
779 yield self.t(endname, **args)
780
780
781 def showbranches(**args):
781 def showbranches(**args):
782 branch = changes[5].get("branch")
782 branch = changes[5].get("branch")
783 if branch != 'default':
783 if branch != 'default':
784 branch = util.tolocal(branch)
784 branch = util.tolocal(branch)
785 return showlist('branch', [branch], plural='branches', **args)
785 return showlist('branch', [branch], plural='branches', **args)
786
786
787 def showparents(**args):
787 def showparents(**args):
788 parents = [[('rev', p), ('node', hex(log.node(p)))]
788 parents = [[('rev', p), ('node', hex(log.node(p)))]
789 for p in self._meaningful_parentrevs(log, rev)]
789 for p in self._meaningful_parentrevs(log, rev)]
790 return showlist('parent', parents, **args)
790 return showlist('parent', parents, **args)
791
791
792 def showtags(**args):
792 def showtags(**args):
793 return showlist('tag', self.repo.nodetags(changenode), **args)
793 return showlist('tag', self.repo.nodetags(changenode), **args)
794
794
795 def showextras(**args):
795 def showextras(**args):
796 for key, value in util.sort(changes[5].items()):
796 for key, value in util.sort(changes[5].items()):
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 matchall(repo)
892 patch = matchfn or matchall(repo)
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[r].changeset())
925 get = util.cachefunc(lambda r: repo[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 m = match(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 not len(repo):
983 if not len(repo):
984 return [], m
984 return [], m
985
985
986 if follow:
986 if follow:
987 defrange = '%s:0' % repo['.'].rev()
987 defrange = '%s:0' % repo['.'].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 = m.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 m.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 = len(repo)
1002 cl_count = len(repo)
1003 if node is None:
1003 if node is None:
1004 last = len(filelog) - 1
1004 last = len(filelog) - 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(j),
1011 revs.append((filelog.linkrev(j),
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 m.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 not len(filelog):
1027 if not len(filelog):
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 if follow:
1031 if follow:
1032 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1032 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1033 slowpath = True
1033 slowpath = True
1034 break
1034 break
1035 else:
1035 else:
1036 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1036 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1037 % (file_, short(node)))
1037 % (file_, short(node)))
1038 continue
1038 continue
1039 for rev, copied in filerevgen(filelog, node):
1039 for rev, copied in filerevgen(filelog, node):
1040 if rev <= maxrev:
1040 if rev <= maxrev:
1041 if rev < minrev:
1041 if rev < minrev:
1042 break
1042 break
1043 fncache.setdefault(rev, [])
1043 fncache.setdefault(rev, [])
1044 fncache[rev].append(file_)
1044 fncache[rev].append(file_)
1045 wanted[rev] = 1
1045 wanted[rev] = 1
1046 if follow and copied:
1046 if follow and copied:
1047 copies.append(copied)
1047 copies.append(copied)
1048 if slowpath:
1048 if slowpath:
1049 if follow:
1049 if follow:
1050 raise util.Abort(_('can only follow copies/renames for explicit '
1050 raise util.Abort(_('can only follow copies/renames for explicit '
1051 'file names'))
1051 'file names'))
1052
1052
1053 # The slow path checks files modified in every changeset.
1053 # The slow path checks files modified in every changeset.
1054 def changerevgen():
1054 def changerevgen():
1055 for i, window in increasing_windows(len(repo) - 1, nullrev):
1055 for i, window in increasing_windows(len(repo) - 1, nullrev):
1056 for j in xrange(i - window, i + 1):
1056 for j in xrange(i - window, i + 1):
1057 yield j, change(j)[3]
1057 yield j, change(j)[3]
1058
1058
1059 for rev, changefiles in changerevgen():
1059 for rev, changefiles in changerevgen():
1060 matches = filter(m, changefiles)
1060 matches = filter(m, changefiles)
1061 if matches:
1061 if matches:
1062 fncache[rev] = matches
1062 fncache[rev] = matches
1063 wanted[rev] = 1
1063 wanted[rev] = 1
1064
1064
1065 class followfilter:
1065 class followfilter:
1066 def __init__(self, onlyfirst=False):
1066 def __init__(self, onlyfirst=False):
1067 self.startrev = nullrev
1067 self.startrev = nullrev
1068 self.roots = []
1068 self.roots = []
1069 self.onlyfirst = onlyfirst
1069 self.onlyfirst = onlyfirst
1070
1070
1071 def match(self, rev):
1071 def match(self, rev):
1072 def realparents(rev):
1072 def realparents(rev):
1073 if self.onlyfirst:
1073 if self.onlyfirst:
1074 return repo.changelog.parentrevs(rev)[0:1]
1074 return repo.changelog.parentrevs(rev)[0:1]
1075 else:
1075 else:
1076 return filter(lambda x: x != nullrev,
1076 return filter(lambda x: x != nullrev,
1077 repo.changelog.parentrevs(rev))
1077 repo.changelog.parentrevs(rev))
1078
1078
1079 if self.startrev == nullrev:
1079 if self.startrev == nullrev:
1080 self.startrev = rev
1080 self.startrev = rev
1081 return True
1081 return True
1082
1082
1083 if rev > self.startrev:
1083 if rev > self.startrev:
1084 # forward: all descendants
1084 # forward: all descendants
1085 if not self.roots:
1085 if not self.roots:
1086 self.roots.append(self.startrev)
1086 self.roots.append(self.startrev)
1087 for parent in realparents(rev):
1087 for parent in realparents(rev):
1088 if parent in self.roots:
1088 if parent in self.roots:
1089 self.roots.append(rev)
1089 self.roots.append(rev)
1090 return True
1090 return True
1091 else:
1091 else:
1092 # backwards: all parents
1092 # backwards: all parents
1093 if not self.roots:
1093 if not self.roots:
1094 self.roots.extend(realparents(self.startrev))
1094 self.roots.extend(realparents(self.startrev))
1095 if rev in self.roots:
1095 if rev in self.roots:
1096 self.roots.remove(rev)
1096 self.roots.remove(rev)
1097 self.roots.extend(realparents(rev))
1097 self.roots.extend(realparents(rev))
1098 return True
1098 return True
1099
1099
1100 return False
1100 return False
1101
1101
1102 # it might be worthwhile to do this in the iterator if the rev range
1102 # it might be worthwhile to do this in the iterator if the rev range
1103 # is descending and the prune args are all within that range
1103 # is descending and the prune args are all within that range
1104 for rev in opts.get('prune', ()):
1104 for rev in opts.get('prune', ()):
1105 rev = repo.changelog.rev(repo.lookup(rev))
1105 rev = repo.changelog.rev(repo.lookup(rev))
1106 ff = followfilter()
1106 ff = followfilter()
1107 stop = min(revs[0], revs[-1])
1107 stop = min(revs[0], revs[-1])
1108 for x in xrange(rev, stop-1, -1):
1108 for x in xrange(rev, stop-1, -1):
1109 if ff.match(x) and x in wanted:
1109 if ff.match(x) and x in wanted:
1110 del wanted[x]
1110 del wanted[x]
1111
1111
1112 def iterate():
1112 def iterate():
1113 if follow and not m.files():
1113 if follow and not m.files():
1114 ff = followfilter(onlyfirst=opts.get('follow_first'))
1114 ff = followfilter(onlyfirst=opts.get('follow_first'))
1115 def want(rev):
1115 def want(rev):
1116 if ff.match(rev) and rev in wanted:
1116 if ff.match(rev) and rev in wanted:
1117 return True
1117 return True
1118 return False
1118 return False
1119 else:
1119 else:
1120 def want(rev):
1120 def want(rev):
1121 return rev in wanted
1121 return rev in wanted
1122
1122
1123 for i, window in increasing_windows(0, len(revs)):
1123 for i, window in increasing_windows(0, len(revs)):
1124 yield 'window', revs[0] < revs[-1], revs[-1]
1124 yield 'window', revs[0] < revs[-1], revs[-1]
1125 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1125 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1126 for rev in util.sort(list(nrevs)):
1126 for rev in util.sort(list(nrevs)):
1127 fns = fncache.get(rev)
1127 fns = fncache.get(rev)
1128 if not fns:
1128 if not fns:
1129 def fns_generator():
1129 def fns_generator():
1130 for f in change(rev)[3]:
1130 for f in change(rev)[3]:
1131 if m(f):
1131 if m(f):
1132 yield f
1132 yield f
1133 fns = fns_generator()
1133 fns = fns_generator()
1134 yield 'add', rev, fns
1134 yield 'add', rev, fns
1135 for rev in nrevs:
1135 for rev in nrevs:
1136 yield 'iter', rev, None
1136 yield 'iter', rev, None
1137 return iterate(), m
1137 return iterate(), m
1138
1138
1139 def commit(ui, repo, commitfunc, pats, opts):
1139 def commit(ui, repo, commitfunc, pats, opts):
1140 '''commit the specified files or all outstanding changes'''
1140 '''commit the specified files or all outstanding changes'''
1141 date = opts.get('date')
1141 date = opts.get('date')
1142 if date:
1142 if date:
1143 opts['date'] = util.parsedate(date)
1143 opts['date'] = util.parsedate(date)
1144 message = logmessage(opts)
1144 message = logmessage(opts)
1145
1145
1146 # extract addremove carefully -- this function can be called from a command
1146 # extract addremove carefully -- this function can be called from a command
1147 # that doesn't support addremove
1147 # that doesn't support addremove
1148 if opts.get('addremove'):
1148 if opts.get('addremove'):
1149 addremove(repo, pats, opts)
1149 addremove(repo, pats, opts)
1150
1150
1151 m = match(repo, pats, opts)
1151 m = match(repo, pats, opts)
1152 if pats:
1152 if pats:
1153 modified, added, removed = repo.status(match=m)[:3]
1153 modified, added, removed = repo.status(match=m)[:3]
1154 files = util.sort(modified + added + removed)
1154 files = util.sort(modified + added + removed)
1155
1155
1156 def is_dir(f):
1156 def is_dir(f):
1157 name = f + '/'
1157 name = f + '/'
1158 i = bisect.bisect(files, name)
1158 i = bisect.bisect(files, name)
1159 return i < len(files) and files[i].startswith(name)
1159 return i < len(files) and files[i].startswith(name)
1160
1160
1161 for f in m.files():
1161 for f in m.files():
1162 if f == '.':
1162 if f == '.':
1163 continue
1163 continue
1164 if f not in files:
1164 if f not in files:
1165 rf = repo.wjoin(f)
1165 rf = repo.wjoin(f)
1166 rel = repo.pathto(f)
1166 rel = repo.pathto(f)
1167 try:
1167 try:
1168 mode = os.lstat(rf)[stat.ST_MODE]
1168 mode = os.lstat(rf)[stat.ST_MODE]
1169 except OSError:
1169 except OSError:
1170 if is_dir(f): # deleted directory ?
1170 if is_dir(f): # deleted directory ?
1171 continue
1171 continue
1172 raise util.Abort(_("file %s not found!") % rel)
1172 raise util.Abort(_("file %s not found!") % rel)
1173 if stat.S_ISDIR(mode):
1173 if stat.S_ISDIR(mode):
1174 if not is_dir(f):
1174 if not is_dir(f):
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 m = matchfiles(repo, files)
1182 m = matchfiles(repo, files)
1183 try:
1183 try:
1184 return commitfunc(ui, repo, message, m, opts)
1184 return commitfunc(ui, repo, message, m, opts)
1185 except ValueError, inst:
1185 except ValueError, inst:
1186 raise util.Abort(str(inst))
1186 raise util.Abort(str(inst))
General Comments 0
You need to be logged in to leave comments. Login now