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