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