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