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