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