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