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