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