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