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