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