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