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