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