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