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