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