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