##// END OF EJS Templates
remove unused "head" hack from util._matcher
Alexis S. L. Carvalho -
r4197:492d0d5b default
parent child Browse files
Show More
@@ -1,770 +1,770
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), 'os sys')
11 demandload(globals(), 'os sys')
12 demandload(globals(), 'mdiff util templater patch')
12 demandload(globals(), 'mdiff util templater patch')
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def revpair(repo, revs):
16 def revpair(repo, revs):
17 '''return pair of nodes, given list of revisions. second item can
17 '''return pair of nodes, given list of revisions. second item can
18 be None, meaning use working dir.'''
18 be None, meaning use working dir.'''
19
19
20 def revfix(repo, val, defval):
20 def revfix(repo, val, defval):
21 if not val and val != 0 and defval is not None:
21 if not val and val != 0 and defval is not None:
22 val = defval
22 val = defval
23 return repo.lookup(val)
23 return repo.lookup(val)
24
24
25 if not revs:
25 if not revs:
26 return repo.dirstate.parents()[0], None
26 return repo.dirstate.parents()[0], None
27 end = None
27 end = None
28 if len(revs) == 1:
28 if len(revs) == 1:
29 if revrangesep in revs[0]:
29 if revrangesep in revs[0]:
30 start, end = revs[0].split(revrangesep, 1)
30 start, end = revs[0].split(revrangesep, 1)
31 start = revfix(repo, start, 0)
31 start = revfix(repo, start, 0)
32 end = revfix(repo, end, repo.changelog.count() - 1)
32 end = revfix(repo, end, repo.changelog.count() - 1)
33 else:
33 else:
34 start = revfix(repo, revs[0], None)
34 start = revfix(repo, revs[0], None)
35 elif len(revs) == 2:
35 elif len(revs) == 2:
36 if revrangesep in revs[0] or revrangesep in revs[1]:
36 if revrangesep in revs[0] or revrangesep in revs[1]:
37 raise util.Abort(_('too many revisions specified'))
37 raise util.Abort(_('too many revisions specified'))
38 start = revfix(repo, revs[0], None)
38 start = revfix(repo, revs[0], None)
39 end = revfix(repo, revs[1], None)
39 end = revfix(repo, revs[1], None)
40 else:
40 else:
41 raise util.Abort(_('too many revisions specified'))
41 raise util.Abort(_('too many revisions specified'))
42 return start, end
42 return start, end
43
43
44 def revrange(repo, revs):
44 def revrange(repo, revs):
45 """Yield revision as strings from a list of revision specifications."""
45 """Yield revision as strings from a list of revision specifications."""
46
46
47 def revfix(repo, val, defval):
47 def revfix(repo, val, defval):
48 if not val and val != 0 and defval is not None:
48 if not val and val != 0 and defval is not None:
49 return defval
49 return defval
50 return repo.changelog.rev(repo.lookup(val))
50 return repo.changelog.rev(repo.lookup(val))
51
51
52 seen, l = {}, []
52 seen, l = {}, []
53 for spec in revs:
53 for spec in revs:
54 if revrangesep in spec:
54 if revrangesep in spec:
55 start, end = spec.split(revrangesep, 1)
55 start, end = spec.split(revrangesep, 1)
56 start = revfix(repo, start, 0)
56 start = revfix(repo, start, 0)
57 end = revfix(repo, end, repo.changelog.count() - 1)
57 end = revfix(repo, end, repo.changelog.count() - 1)
58 step = start > end and -1 or 1
58 step = start > end and -1 or 1
59 for rev in xrange(start, end+step, step):
59 for rev in xrange(start, end+step, step):
60 if rev in seen:
60 if rev in seen:
61 continue
61 continue
62 seen[rev] = 1
62 seen[rev] = 1
63 l.append(rev)
63 l.append(rev)
64 else:
64 else:
65 rev = revfix(repo, spec, None)
65 rev = revfix(repo, spec, None)
66 if rev in seen:
66 if rev in seen:
67 continue
67 continue
68 seen[rev] = 1
68 seen[rev] = 1
69 l.append(rev)
69 l.append(rev)
70
70
71 return l
71 return l
72
72
73 def make_filename(repo, pat, node,
73 def make_filename(repo, pat, node,
74 total=None, seqno=None, revwidth=None, pathname=None):
74 total=None, seqno=None, revwidth=None, pathname=None):
75 node_expander = {
75 node_expander = {
76 'H': lambda: hex(node),
76 'H': lambda: hex(node),
77 'R': lambda: str(repo.changelog.rev(node)),
77 'R': lambda: str(repo.changelog.rev(node)),
78 'h': lambda: short(node),
78 'h': lambda: short(node),
79 }
79 }
80 expander = {
80 expander = {
81 '%': lambda: '%',
81 '%': lambda: '%',
82 'b': lambda: os.path.basename(repo.root),
82 'b': lambda: os.path.basename(repo.root),
83 }
83 }
84
84
85 try:
85 try:
86 if node:
86 if node:
87 expander.update(node_expander)
87 expander.update(node_expander)
88 if node and revwidth is not None:
88 if node and revwidth is not None:
89 expander['r'] = (lambda:
89 expander['r'] = (lambda:
90 str(repo.changelog.rev(node)).zfill(revwidth))
90 str(repo.changelog.rev(node)).zfill(revwidth))
91 if total is not None:
91 if total is not None:
92 expander['N'] = lambda: str(total)
92 expander['N'] = lambda: str(total)
93 if seqno is not None:
93 if seqno is not None:
94 expander['n'] = lambda: str(seqno)
94 expander['n'] = lambda: str(seqno)
95 if total is not None and seqno is not None:
95 if total is not None and seqno is not None:
96 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
96 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
97 if pathname is not None:
97 if pathname is not None:
98 expander['s'] = lambda: os.path.basename(pathname)
98 expander['s'] = lambda: os.path.basename(pathname)
99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
100 expander['p'] = lambda: pathname
100 expander['p'] = lambda: pathname
101
101
102 newname = []
102 newname = []
103 patlen = len(pat)
103 patlen = len(pat)
104 i = 0
104 i = 0
105 while i < patlen:
105 while i < patlen:
106 c = pat[i]
106 c = pat[i]
107 if c == '%':
107 if c == '%':
108 i += 1
108 i += 1
109 c = pat[i]
109 c = pat[i]
110 c = expander[c]()
110 c = expander[c]()
111 newname.append(c)
111 newname.append(c)
112 i += 1
112 i += 1
113 return ''.join(newname)
113 return ''.join(newname)
114 except KeyError, inst:
114 except KeyError, inst:
115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
116 inst.args[0])
116 inst.args[0])
117
117
118 def make_file(repo, pat, node=None,
118 def make_file(repo, pat, node=None,
119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
120 if not pat or pat == '-':
120 if not pat or pat == '-':
121 return 'w' in mode and sys.stdout or sys.stdin
121 return 'w' in mode and sys.stdout or sys.stdin
122 if hasattr(pat, 'write') and 'w' in mode:
122 if hasattr(pat, 'write') and 'w' in mode:
123 return pat
123 return pat
124 if hasattr(pat, 'read') and 'r' in mode:
124 if hasattr(pat, 'read') and 'r' in mode:
125 return pat
125 return pat
126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
127 pathname),
127 pathname),
128 mode)
128 mode)
129
129
130 def matchpats(repo, pats=[], opts={}, head='', globbed=False, default=None):
130 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
131 cwd = repo.getcwd()
131 cwd = repo.getcwd()
132 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
132 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
133 opts.get('exclude'), head, globbed=globbed,
133 opts.get('exclude'), globbed=globbed,
134 default=default)
134 default=default)
135
135
136 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None,
136 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
137 globbed=False, default=None):
137 default=None):
138 files, matchfn, anypats = matchpats(repo, pats, opts, head,
138 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
139 globbed=globbed, default=default)
139 default=default)
140 exact = dict.fromkeys(files)
140 exact = dict.fromkeys(files)
141 for src, fn in repo.walk(node=node, files=files, match=matchfn,
141 for src, fn in repo.walk(node=node, files=files, match=matchfn,
142 badmatch=badmatch):
142 badmatch=badmatch):
143 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
143 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
144
144
145 def findrenames(repo, added=None, removed=None, threshold=0.5):
145 def findrenames(repo, added=None, removed=None, threshold=0.5):
146 if added is None or removed is None:
146 if added is None or removed is None:
147 added, removed = repo.status()[1:3]
147 added, removed = repo.status()[1:3]
148 changes = repo.changelog.read(repo.dirstate.parents()[0])
148 changes = repo.changelog.read(repo.dirstate.parents()[0])
149 mf = repo.manifest.read(changes[0])
149 mf = repo.manifest.read(changes[0])
150 for a in added:
150 for a in added:
151 aa = repo.wread(a)
151 aa = repo.wread(a)
152 bestscore, bestname = None, None
152 bestscore, bestname = None, None
153 for r in removed:
153 for r in removed:
154 rr = repo.file(r).read(mf[r])
154 rr = repo.file(r).read(mf[r])
155 delta = mdiff.textdiff(aa, rr)
155 delta = mdiff.textdiff(aa, rr)
156 if len(delta) < len(aa):
156 if len(delta) < len(aa):
157 myscore = 1.0 - (float(len(delta)) / len(aa))
157 myscore = 1.0 - (float(len(delta)) / len(aa))
158 if bestscore is None or myscore > bestscore:
158 if bestscore is None or myscore > bestscore:
159 bestscore, bestname = myscore, r
159 bestscore, bestname = myscore, r
160 if bestname and bestscore >= threshold:
160 if bestname and bestscore >= threshold:
161 yield bestname, a, bestscore
161 yield bestname, a, bestscore
162
162
163 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
163 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
164 similarity=None):
164 similarity=None):
165 if dry_run is None:
165 if dry_run is None:
166 dry_run = opts.get('dry_run')
166 dry_run = opts.get('dry_run')
167 if similarity is None:
167 if similarity is None:
168 similarity = float(opts.get('similarity') or 0)
168 similarity = float(opts.get('similarity') or 0)
169 add, remove = [], []
169 add, remove = [], []
170 mapping = {}
170 mapping = {}
171 for src, abs, rel, exact in walk(repo, pats, opts):
171 for src, abs, rel, exact in walk(repo, pats, opts):
172 if src == 'f' and repo.dirstate.state(abs) == '?':
172 if src == 'f' and repo.dirstate.state(abs) == '?':
173 add.append(abs)
173 add.append(abs)
174 mapping[abs] = rel, exact
174 mapping[abs] = rel, exact
175 if repo.ui.verbose or not exact:
175 if repo.ui.verbose or not exact:
176 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
176 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
177 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
177 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
178 remove.append(abs)
178 remove.append(abs)
179 mapping[abs] = rel, exact
179 mapping[abs] = rel, exact
180 if repo.ui.verbose or not exact:
180 if repo.ui.verbose or not exact:
181 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
181 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
182 if not dry_run:
182 if not dry_run:
183 repo.add(add, wlock=wlock)
183 repo.add(add, wlock=wlock)
184 repo.remove(remove, wlock=wlock)
184 repo.remove(remove, wlock=wlock)
185 if similarity > 0:
185 if similarity > 0:
186 for old, new, score in findrenames(repo, add, remove, similarity):
186 for old, new, score in findrenames(repo, add, remove, similarity):
187 oldrel, oldexact = mapping[old]
187 oldrel, oldexact = mapping[old]
188 newrel, newexact = mapping[new]
188 newrel, newexact = mapping[new]
189 if repo.ui.verbose or not oldexact or not newexact:
189 if repo.ui.verbose or not oldexact or not newexact:
190 repo.ui.status(_('recording removal of %s as rename to %s '
190 repo.ui.status(_('recording removal of %s as rename to %s '
191 '(%d%% similar)\n') %
191 '(%d%% similar)\n') %
192 (oldrel, newrel, score * 100))
192 (oldrel, newrel, score * 100))
193 if not dry_run:
193 if not dry_run:
194 repo.copy(old, new, wlock=wlock)
194 repo.copy(old, new, wlock=wlock)
195
195
196 class changeset_printer(object):
196 class changeset_printer(object):
197 '''show changeset information when templating not requested.'''
197 '''show changeset information when templating not requested.'''
198
198
199 def __init__(self, ui, repo, patch, brinfo, buffered):
199 def __init__(self, ui, repo, patch, brinfo, buffered):
200 self.ui = ui
200 self.ui = ui
201 self.repo = repo
201 self.repo = repo
202 self.buffered = buffered
202 self.buffered = buffered
203 self.patch = patch
203 self.patch = patch
204 self.brinfo = brinfo
204 self.brinfo = brinfo
205 self.header = {}
205 self.header = {}
206 self.hunk = {}
206 self.hunk = {}
207 self.lastheader = None
207 self.lastheader = None
208
208
209 def flush(self, rev):
209 def flush(self, rev):
210 if rev in self.header:
210 if rev in self.header:
211 h = self.header[rev]
211 h = self.header[rev]
212 if h != self.lastheader:
212 if h != self.lastheader:
213 self.lastheader = h
213 self.lastheader = h
214 self.ui.write(h)
214 self.ui.write(h)
215 del self.header[rev]
215 del self.header[rev]
216 if rev in self.hunk:
216 if rev in self.hunk:
217 self.ui.write(self.hunk[rev])
217 self.ui.write(self.hunk[rev])
218 del self.hunk[rev]
218 del self.hunk[rev]
219 return 1
219 return 1
220 return 0
220 return 0
221
221
222 def show(self, rev=0, changenode=None, copies=None, **props):
222 def show(self, rev=0, changenode=None, copies=None, **props):
223 if self.buffered:
223 if self.buffered:
224 self.ui.pushbuffer()
224 self.ui.pushbuffer()
225 self._show(rev, changenode, copies, props)
225 self._show(rev, changenode, copies, props)
226 self.hunk[rev] = self.ui.popbuffer()
226 self.hunk[rev] = self.ui.popbuffer()
227 else:
227 else:
228 self._show(rev, changenode, copies, props)
228 self._show(rev, changenode, copies, props)
229
229
230 def _show(self, rev, changenode, copies, props):
230 def _show(self, rev, changenode, copies, props):
231 '''show a single changeset or file revision'''
231 '''show a single changeset or file revision'''
232 log = self.repo.changelog
232 log = self.repo.changelog
233 if changenode is None:
233 if changenode is None:
234 changenode = log.node(rev)
234 changenode = log.node(rev)
235 elif not rev:
235 elif not rev:
236 rev = log.rev(changenode)
236 rev = log.rev(changenode)
237
237
238 if self.ui.quiet:
238 if self.ui.quiet:
239 self.ui.write("%d:%s\n" % (rev, short(changenode)))
239 self.ui.write("%d:%s\n" % (rev, short(changenode)))
240 return
240 return
241
241
242 changes = log.read(changenode)
242 changes = log.read(changenode)
243 date = util.datestr(changes[2])
243 date = util.datestr(changes[2])
244 extra = changes[5]
244 extra = changes[5]
245 branch = extra.get("branch")
245 branch = extra.get("branch")
246
246
247 hexfunc = self.ui.debugflag and hex or short
247 hexfunc = self.ui.debugflag and hex or short
248
248
249 parents = log.parentrevs(rev)
249 parents = log.parentrevs(rev)
250 if not self.ui.debugflag:
250 if not self.ui.debugflag:
251 if parents[1] == nullrev:
251 if parents[1] == nullrev:
252 if parents[0] >= rev - 1:
252 if parents[0] >= rev - 1:
253 parents = []
253 parents = []
254 else:
254 else:
255 parents = [parents[0]]
255 parents = [parents[0]]
256 parents = [(p, hexfunc(log.node(p))) for p in parents]
256 parents = [(p, hexfunc(log.node(p))) for p in parents]
257
257
258 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
258 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
259
259
260 if branch:
260 if branch:
261 branch = util.tolocal(branch)
261 branch = util.tolocal(branch)
262 self.ui.write(_("branch: %s\n") % branch)
262 self.ui.write(_("branch: %s\n") % branch)
263 for tag in self.repo.nodetags(changenode):
263 for tag in self.repo.nodetags(changenode):
264 self.ui.write(_("tag: %s\n") % tag)
264 self.ui.write(_("tag: %s\n") % tag)
265 for parent in parents:
265 for parent in parents:
266 self.ui.write(_("parent: %d:%s\n") % parent)
266 self.ui.write(_("parent: %d:%s\n") % parent)
267
267
268 if self.brinfo:
268 if self.brinfo:
269 br = self.repo.branchlookup([changenode])
269 br = self.repo.branchlookup([changenode])
270 if br:
270 if br:
271 self.ui.write(_("branch: %s\n") % " ".join(br[changenode]))
271 self.ui.write(_("branch: %s\n") % " ".join(br[changenode]))
272
272
273 if self.ui.debugflag:
273 if self.ui.debugflag:
274 self.ui.write(_("manifest: %d:%s\n") %
274 self.ui.write(_("manifest: %d:%s\n") %
275 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
275 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
276 self.ui.write(_("user: %s\n") % changes[1])
276 self.ui.write(_("user: %s\n") % changes[1])
277 self.ui.write(_("date: %s\n") % date)
277 self.ui.write(_("date: %s\n") % date)
278
278
279 if self.ui.debugflag:
279 if self.ui.debugflag:
280 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
280 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
281 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
281 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
282 files):
282 files):
283 if value:
283 if value:
284 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
284 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
285 elif changes[3] and self.ui.verbose:
285 elif changes[3] and self.ui.verbose:
286 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
286 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
287 if copies and self.ui.verbose:
287 if copies and self.ui.verbose:
288 copies = ['%s (%s)' % c for c in copies]
288 copies = ['%s (%s)' % c for c in copies]
289 self.ui.write(_("copies: %s\n") % ' '.join(copies))
289 self.ui.write(_("copies: %s\n") % ' '.join(copies))
290
290
291 if extra and self.ui.debugflag:
291 if extra and self.ui.debugflag:
292 extraitems = extra.items()
292 extraitems = extra.items()
293 extraitems.sort()
293 extraitems.sort()
294 for key, value in extraitems:
294 for key, value in extraitems:
295 self.ui.write(_("extra: %s=%s\n")
295 self.ui.write(_("extra: %s=%s\n")
296 % (key, value.encode('string_escape')))
296 % (key, value.encode('string_escape')))
297
297
298 description = changes[4].strip()
298 description = changes[4].strip()
299 if description:
299 if description:
300 if self.ui.verbose:
300 if self.ui.verbose:
301 self.ui.write(_("description:\n"))
301 self.ui.write(_("description:\n"))
302 self.ui.write(description)
302 self.ui.write(description)
303 self.ui.write("\n\n")
303 self.ui.write("\n\n")
304 else:
304 else:
305 self.ui.write(_("summary: %s\n") %
305 self.ui.write(_("summary: %s\n") %
306 description.splitlines()[0])
306 description.splitlines()[0])
307 self.ui.write("\n")
307 self.ui.write("\n")
308
308
309 self.showpatch(changenode)
309 self.showpatch(changenode)
310
310
311 def showpatch(self, node):
311 def showpatch(self, node):
312 if self.patch:
312 if self.patch:
313 prev = self.repo.changelog.parents(node)[0]
313 prev = self.repo.changelog.parents(node)[0]
314 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
314 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
315 self.ui.write("\n")
315 self.ui.write("\n")
316
316
317 class changeset_templater(changeset_printer):
317 class changeset_templater(changeset_printer):
318 '''format changeset information.'''
318 '''format changeset information.'''
319
319
320 def __init__(self, ui, repo, patch, brinfo, mapfile, buffered):
320 def __init__(self, ui, repo, patch, brinfo, mapfile, buffered):
321 changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered)
321 changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered)
322 self.t = templater.templater(mapfile, templater.common_filters,
322 self.t = templater.templater(mapfile, templater.common_filters,
323 cache={'parent': '{rev}:{node|short} ',
323 cache={'parent': '{rev}:{node|short} ',
324 'manifest': '{rev}:{node|short}',
324 'manifest': '{rev}:{node|short}',
325 'filecopy': '{name} ({source})'})
325 'filecopy': '{name} ({source})'})
326
326
327 def use_template(self, t):
327 def use_template(self, t):
328 '''set template string to use'''
328 '''set template string to use'''
329 self.t.cache['changeset'] = t
329 self.t.cache['changeset'] = t
330
330
331 def _show(self, rev, changenode, copies, props):
331 def _show(self, rev, changenode, copies, props):
332 '''show a single changeset or file revision'''
332 '''show a single changeset or file revision'''
333 log = self.repo.changelog
333 log = self.repo.changelog
334 if changenode is None:
334 if changenode is None:
335 changenode = log.node(rev)
335 changenode = log.node(rev)
336 elif not rev:
336 elif not rev:
337 rev = log.rev(changenode)
337 rev = log.rev(changenode)
338
338
339 changes = log.read(changenode)
339 changes = log.read(changenode)
340
340
341 def showlist(name, values, plural=None, **args):
341 def showlist(name, values, plural=None, **args):
342 '''expand set of values.
342 '''expand set of values.
343 name is name of key in template map.
343 name is name of key in template map.
344 values is list of strings or dicts.
344 values is list of strings or dicts.
345 plural is plural of name, if not simply name + 's'.
345 plural is plural of name, if not simply name + 's'.
346
346
347 expansion works like this, given name 'foo'.
347 expansion works like this, given name 'foo'.
348
348
349 if values is empty, expand 'no_foos'.
349 if values is empty, expand 'no_foos'.
350
350
351 if 'foo' not in template map, return values as a string,
351 if 'foo' not in template map, return values as a string,
352 joined by space.
352 joined by space.
353
353
354 expand 'start_foos'.
354 expand 'start_foos'.
355
355
356 for each value, expand 'foo'. if 'last_foo' in template
356 for each value, expand 'foo'. if 'last_foo' in template
357 map, expand it instead of 'foo' for last key.
357 map, expand it instead of 'foo' for last key.
358
358
359 expand 'end_foos'.
359 expand 'end_foos'.
360 '''
360 '''
361 if plural: names = plural
361 if plural: names = plural
362 else: names = name + 's'
362 else: names = name + 's'
363 if not values:
363 if not values:
364 noname = 'no_' + names
364 noname = 'no_' + names
365 if noname in self.t:
365 if noname in self.t:
366 yield self.t(noname, **args)
366 yield self.t(noname, **args)
367 return
367 return
368 if name not in self.t:
368 if name not in self.t:
369 if isinstance(values[0], str):
369 if isinstance(values[0], str):
370 yield ' '.join(values)
370 yield ' '.join(values)
371 else:
371 else:
372 for v in values:
372 for v in values:
373 yield dict(v, **args)
373 yield dict(v, **args)
374 return
374 return
375 startname = 'start_' + names
375 startname = 'start_' + names
376 if startname in self.t:
376 if startname in self.t:
377 yield self.t(startname, **args)
377 yield self.t(startname, **args)
378 vargs = args.copy()
378 vargs = args.copy()
379 def one(v, tag=name):
379 def one(v, tag=name):
380 try:
380 try:
381 vargs.update(v)
381 vargs.update(v)
382 except (AttributeError, ValueError):
382 except (AttributeError, ValueError):
383 try:
383 try:
384 for a, b in v:
384 for a, b in v:
385 vargs[a] = b
385 vargs[a] = b
386 except ValueError:
386 except ValueError:
387 vargs[name] = v
387 vargs[name] = v
388 return self.t(tag, **vargs)
388 return self.t(tag, **vargs)
389 lastname = 'last_' + name
389 lastname = 'last_' + name
390 if lastname in self.t:
390 if lastname in self.t:
391 last = values.pop()
391 last = values.pop()
392 else:
392 else:
393 last = None
393 last = None
394 for v in values:
394 for v in values:
395 yield one(v)
395 yield one(v)
396 if last is not None:
396 if last is not None:
397 yield one(last, tag=lastname)
397 yield one(last, tag=lastname)
398 endname = 'end_' + names
398 endname = 'end_' + names
399 if endname in self.t:
399 if endname in self.t:
400 yield self.t(endname, **args)
400 yield self.t(endname, **args)
401
401
402 def showbranches(**args):
402 def showbranches(**args):
403 branch = changes[5].get("branch")
403 branch = changes[5].get("branch")
404 if branch:
404 if branch:
405 branch = util.tolocal(branch)
405 branch = util.tolocal(branch)
406 return showlist('branch', [branch], plural='branches', **args)
406 return showlist('branch', [branch], plural='branches', **args)
407 # add old style branches if requested
407 # add old style branches if requested
408 if self.brinfo:
408 if self.brinfo:
409 br = self.repo.branchlookup([changenode])
409 br = self.repo.branchlookup([changenode])
410 if changenode in br:
410 if changenode in br:
411 return showlist('branch', br[changenode],
411 return showlist('branch', br[changenode],
412 plural='branches', **args)
412 plural='branches', **args)
413
413
414 def showparents(**args):
414 def showparents(**args):
415 parents = [[('rev', log.rev(p)), ('node', hex(p))]
415 parents = [[('rev', log.rev(p)), ('node', hex(p))]
416 for p in log.parents(changenode)
416 for p in log.parents(changenode)
417 if self.ui.debugflag or p != nullid]
417 if self.ui.debugflag or p != nullid]
418 if (not self.ui.debugflag and len(parents) == 1 and
418 if (not self.ui.debugflag and len(parents) == 1 and
419 parents[0][0][1] == rev - 1):
419 parents[0][0][1] == rev - 1):
420 return
420 return
421 return showlist('parent', parents, **args)
421 return showlist('parent', parents, **args)
422
422
423 def showtags(**args):
423 def showtags(**args):
424 return showlist('tag', self.repo.nodetags(changenode), **args)
424 return showlist('tag', self.repo.nodetags(changenode), **args)
425
425
426 def showextras(**args):
426 def showextras(**args):
427 extras = changes[5].items()
427 extras = changes[5].items()
428 extras.sort()
428 extras.sort()
429 for key, value in extras:
429 for key, value in extras:
430 args = args.copy()
430 args = args.copy()
431 args.update(dict(key=key, value=value))
431 args.update(dict(key=key, value=value))
432 yield self.t('extra', **args)
432 yield self.t('extra', **args)
433
433
434 def showcopies(**args):
434 def showcopies(**args):
435 c = [{'name': x[0], 'source': x[1]} for x in copies]
435 c = [{'name': x[0], 'source': x[1]} for x in copies]
436 return showlist('file_copy', c, plural='file_copies', **args)
436 return showlist('file_copy', c, plural='file_copies', **args)
437
437
438 if self.ui.debugflag:
438 if self.ui.debugflag:
439 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
439 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
440 def showfiles(**args):
440 def showfiles(**args):
441 return showlist('file', files[0], **args)
441 return showlist('file', files[0], **args)
442 def showadds(**args):
442 def showadds(**args):
443 return showlist('file_add', files[1], **args)
443 return showlist('file_add', files[1], **args)
444 def showdels(**args):
444 def showdels(**args):
445 return showlist('file_del', files[2], **args)
445 return showlist('file_del', files[2], **args)
446 def showmanifest(**args):
446 def showmanifest(**args):
447 args = args.copy()
447 args = args.copy()
448 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
448 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
449 node=hex(changes[0])))
449 node=hex(changes[0])))
450 return self.t('manifest', **args)
450 return self.t('manifest', **args)
451 else:
451 else:
452 def showfiles(**args):
452 def showfiles(**args):
453 return showlist('file', changes[3], **args)
453 return showlist('file', changes[3], **args)
454 showadds = ''
454 showadds = ''
455 showdels = ''
455 showdels = ''
456 showmanifest = ''
456 showmanifest = ''
457
457
458 defprops = {
458 defprops = {
459 'author': changes[1],
459 'author': changes[1],
460 'branches': showbranches,
460 'branches': showbranches,
461 'date': changes[2],
461 'date': changes[2],
462 'desc': changes[4],
462 'desc': changes[4],
463 'file_adds': showadds,
463 'file_adds': showadds,
464 'file_dels': showdels,
464 'file_dels': showdels,
465 'files': showfiles,
465 'files': showfiles,
466 'file_copies': showcopies,
466 'file_copies': showcopies,
467 'manifest': showmanifest,
467 'manifest': showmanifest,
468 'node': hex(changenode),
468 'node': hex(changenode),
469 'parents': showparents,
469 'parents': showparents,
470 'rev': rev,
470 'rev': rev,
471 'tags': showtags,
471 'tags': showtags,
472 'extras': showextras,
472 'extras': showextras,
473 }
473 }
474 props = props.copy()
474 props = props.copy()
475 props.update(defprops)
475 props.update(defprops)
476
476
477 try:
477 try:
478 if self.ui.debugflag and 'header_debug' in self.t:
478 if self.ui.debugflag and 'header_debug' in self.t:
479 key = 'header_debug'
479 key = 'header_debug'
480 elif self.ui.quiet and 'header_quiet' in self.t:
480 elif self.ui.quiet and 'header_quiet' in self.t:
481 key = 'header_quiet'
481 key = 'header_quiet'
482 elif self.ui.verbose and 'header_verbose' in self.t:
482 elif self.ui.verbose and 'header_verbose' in self.t:
483 key = 'header_verbose'
483 key = 'header_verbose'
484 elif 'header' in self.t:
484 elif 'header' in self.t:
485 key = 'header'
485 key = 'header'
486 else:
486 else:
487 key = ''
487 key = ''
488 if key:
488 if key:
489 h = templater.stringify(self.t(key, **props))
489 h = templater.stringify(self.t(key, **props))
490 if self.buffered:
490 if self.buffered:
491 self.header[rev] = h
491 self.header[rev] = h
492 else:
492 else:
493 self.ui.write(h)
493 self.ui.write(h)
494 if self.ui.debugflag and 'changeset_debug' in self.t:
494 if self.ui.debugflag and 'changeset_debug' in self.t:
495 key = 'changeset_debug'
495 key = 'changeset_debug'
496 elif self.ui.quiet and 'changeset_quiet' in self.t:
496 elif self.ui.quiet and 'changeset_quiet' in self.t:
497 key = 'changeset_quiet'
497 key = 'changeset_quiet'
498 elif self.ui.verbose and 'changeset_verbose' in self.t:
498 elif self.ui.verbose and 'changeset_verbose' in self.t:
499 key = 'changeset_verbose'
499 key = 'changeset_verbose'
500 else:
500 else:
501 key = 'changeset'
501 key = 'changeset'
502 self.ui.write(templater.stringify(self.t(key, **props)))
502 self.ui.write(templater.stringify(self.t(key, **props)))
503 self.showpatch(changenode)
503 self.showpatch(changenode)
504 except KeyError, inst:
504 except KeyError, inst:
505 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
505 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
506 inst.args[0]))
506 inst.args[0]))
507 except SyntaxError, inst:
507 except SyntaxError, inst:
508 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
508 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
509
509
510 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
510 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
511 """show one changeset using template or regular display.
511 """show one changeset using template or regular display.
512
512
513 Display format will be the first non-empty hit of:
513 Display format will be the first non-empty hit of:
514 1. option 'template'
514 1. option 'template'
515 2. option 'style'
515 2. option 'style'
516 3. [ui] setting 'logtemplate'
516 3. [ui] setting 'logtemplate'
517 4. [ui] setting 'style'
517 4. [ui] setting 'style'
518 If all of these values are either the unset or the empty string,
518 If all of these values are either the unset or the empty string,
519 regular display via changeset_printer() is done.
519 regular display via changeset_printer() is done.
520 """
520 """
521 # options
521 # options
522 patch = False
522 patch = False
523 if opts.get('patch'):
523 if opts.get('patch'):
524 patch = matchfn or util.always
524 patch = matchfn or util.always
525
525
526 br = None
526 br = None
527 if opts.get('branches'):
527 if opts.get('branches'):
528 ui.warn(_("the --branches option is deprecated, "
528 ui.warn(_("the --branches option is deprecated, "
529 "please use 'hg branches' instead\n"))
529 "please use 'hg branches' instead\n"))
530 br = True
530 br = True
531 tmpl = opts.get('template')
531 tmpl = opts.get('template')
532 mapfile = None
532 mapfile = None
533 if tmpl:
533 if tmpl:
534 tmpl = templater.parsestring(tmpl, quoted=False)
534 tmpl = templater.parsestring(tmpl, quoted=False)
535 else:
535 else:
536 mapfile = opts.get('style')
536 mapfile = opts.get('style')
537 # ui settings
537 # ui settings
538 if not mapfile:
538 if not mapfile:
539 tmpl = ui.config('ui', 'logtemplate')
539 tmpl = ui.config('ui', 'logtemplate')
540 if tmpl:
540 if tmpl:
541 tmpl = templater.parsestring(tmpl)
541 tmpl = templater.parsestring(tmpl)
542 else:
542 else:
543 mapfile = ui.config('ui', 'style')
543 mapfile = ui.config('ui', 'style')
544
544
545 if tmpl or mapfile:
545 if tmpl or mapfile:
546 if mapfile:
546 if mapfile:
547 if not os.path.split(mapfile)[0]:
547 if not os.path.split(mapfile)[0]:
548 mapname = (templater.templatepath('map-cmdline.' + mapfile)
548 mapname = (templater.templatepath('map-cmdline.' + mapfile)
549 or templater.templatepath(mapfile))
549 or templater.templatepath(mapfile))
550 if mapname: mapfile = mapname
550 if mapname: mapfile = mapname
551 try:
551 try:
552 t = changeset_templater(ui, repo, patch, br, mapfile, buffered)
552 t = changeset_templater(ui, repo, patch, br, mapfile, buffered)
553 except SyntaxError, inst:
553 except SyntaxError, inst:
554 raise util.Abort(inst.args[0])
554 raise util.Abort(inst.args[0])
555 if tmpl: t.use_template(tmpl)
555 if tmpl: t.use_template(tmpl)
556 return t
556 return t
557 return changeset_printer(ui, repo, patch, br, buffered)
557 return changeset_printer(ui, repo, patch, br, buffered)
558
558
559 def finddate(ui, repo, date):
559 def finddate(ui, repo, date):
560 """Find the tipmost changeset that matches the given date spec"""
560 """Find the tipmost changeset that matches the given date spec"""
561 df = util.matchdate(date + " to " + date)
561 df = util.matchdate(date + " to " + date)
562 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
562 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
563 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
563 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
564 results = {}
564 results = {}
565 for st, rev, fns in changeiter:
565 for st, rev, fns in changeiter:
566 if st == 'add':
566 if st == 'add':
567 d = get(rev)[2]
567 d = get(rev)[2]
568 if df(d[0]):
568 if df(d[0]):
569 results[rev] = d
569 results[rev] = d
570 elif st == 'iter':
570 elif st == 'iter':
571 if rev in results:
571 if rev in results:
572 ui.status("Found revision %s from %s\n" %
572 ui.status("Found revision %s from %s\n" %
573 (rev, util.datestr(results[rev])))
573 (rev, util.datestr(results[rev])))
574 return str(rev)
574 return str(rev)
575
575
576 raise util.Abort(_("revision matching date not found"))
576 raise util.Abort(_("revision matching date not found"))
577
577
578 def walkchangerevs(ui, repo, pats, change, opts):
578 def walkchangerevs(ui, repo, pats, change, opts):
579 '''Iterate over files and the revs they changed in.
579 '''Iterate over files and the revs they changed in.
580
580
581 Callers most commonly need to iterate backwards over the history
581 Callers most commonly need to iterate backwards over the history
582 it is interested in. Doing so has awful (quadratic-looking)
582 it is interested in. Doing so has awful (quadratic-looking)
583 performance, so we use iterators in a "windowed" way.
583 performance, so we use iterators in a "windowed" way.
584
584
585 We walk a window of revisions in the desired order. Within the
585 We walk a window of revisions in the desired order. Within the
586 window, we first walk forwards to gather data, then in the desired
586 window, we first walk forwards to gather data, then in the desired
587 order (usually backwards) to display it.
587 order (usually backwards) to display it.
588
588
589 This function returns an (iterator, matchfn) tuple. The iterator
589 This function returns an (iterator, matchfn) tuple. The iterator
590 yields 3-tuples. They will be of one of the following forms:
590 yields 3-tuples. They will be of one of the following forms:
591
591
592 "window", incrementing, lastrev: stepping through a window,
592 "window", incrementing, lastrev: stepping through a window,
593 positive if walking forwards through revs, last rev in the
593 positive if walking forwards through revs, last rev in the
594 sequence iterated over - use to reset state for the current window
594 sequence iterated over - use to reset state for the current window
595
595
596 "add", rev, fns: out-of-order traversal of the given file names
596 "add", rev, fns: out-of-order traversal of the given file names
597 fns, which changed during revision rev - use to gather data for
597 fns, which changed during revision rev - use to gather data for
598 possible display
598 possible display
599
599
600 "iter", rev, None: in-order traversal of the revs earlier iterated
600 "iter", rev, None: in-order traversal of the revs earlier iterated
601 over with "add" - use to display data'''
601 over with "add" - use to display data'''
602
602
603 def increasing_windows(start, end, windowsize=8, sizelimit=512):
603 def increasing_windows(start, end, windowsize=8, sizelimit=512):
604 if start < end:
604 if start < end:
605 while start < end:
605 while start < end:
606 yield start, min(windowsize, end-start)
606 yield start, min(windowsize, end-start)
607 start += windowsize
607 start += windowsize
608 if windowsize < sizelimit:
608 if windowsize < sizelimit:
609 windowsize *= 2
609 windowsize *= 2
610 else:
610 else:
611 while start > end:
611 while start > end:
612 yield start, min(windowsize, start-end-1)
612 yield start, min(windowsize, start-end-1)
613 start -= windowsize
613 start -= windowsize
614 if windowsize < sizelimit:
614 if windowsize < sizelimit:
615 windowsize *= 2
615 windowsize *= 2
616
616
617 files, matchfn, anypats = matchpats(repo, pats, opts)
617 files, matchfn, anypats = matchpats(repo, pats, opts)
618 follow = opts.get('follow') or opts.get('follow_first')
618 follow = opts.get('follow') or opts.get('follow_first')
619
619
620 if repo.changelog.count() == 0:
620 if repo.changelog.count() == 0:
621 return [], matchfn
621 return [], matchfn
622
622
623 if follow:
623 if follow:
624 defrange = '%s:0' % repo.changectx().rev()
624 defrange = '%s:0' % repo.changectx().rev()
625 else:
625 else:
626 defrange = 'tip:0'
626 defrange = 'tip:0'
627 revs = revrange(repo, opts['rev'] or [defrange])
627 revs = revrange(repo, opts['rev'] or [defrange])
628 wanted = {}
628 wanted = {}
629 slowpath = anypats or opts.get('removed')
629 slowpath = anypats or opts.get('removed')
630 fncache = {}
630 fncache = {}
631
631
632 if not slowpath and not files:
632 if not slowpath and not files:
633 # No files, no patterns. Display all revs.
633 # No files, no patterns. Display all revs.
634 wanted = dict.fromkeys(revs)
634 wanted = dict.fromkeys(revs)
635 copies = []
635 copies = []
636 if not slowpath:
636 if not slowpath:
637 # Only files, no patterns. Check the history of each file.
637 # Only files, no patterns. Check the history of each file.
638 def filerevgen(filelog, node):
638 def filerevgen(filelog, node):
639 cl_count = repo.changelog.count()
639 cl_count = repo.changelog.count()
640 if node is None:
640 if node is None:
641 last = filelog.count() - 1
641 last = filelog.count() - 1
642 else:
642 else:
643 last = filelog.rev(node)
643 last = filelog.rev(node)
644 for i, window in increasing_windows(last, nullrev):
644 for i, window in increasing_windows(last, nullrev):
645 revs = []
645 revs = []
646 for j in xrange(i - window, i + 1):
646 for j in xrange(i - window, i + 1):
647 n = filelog.node(j)
647 n = filelog.node(j)
648 revs.append((filelog.linkrev(n),
648 revs.append((filelog.linkrev(n),
649 follow and filelog.renamed(n)))
649 follow and filelog.renamed(n)))
650 revs.reverse()
650 revs.reverse()
651 for rev in revs:
651 for rev in revs:
652 # only yield rev for which we have the changelog, it can
652 # only yield rev for which we have the changelog, it can
653 # happen while doing "hg log" during a pull or commit
653 # happen while doing "hg log" during a pull or commit
654 if rev[0] < cl_count:
654 if rev[0] < cl_count:
655 yield rev
655 yield rev
656 def iterfiles():
656 def iterfiles():
657 for filename in files:
657 for filename in files:
658 yield filename, None
658 yield filename, None
659 for filename_node in copies:
659 for filename_node in copies:
660 yield filename_node
660 yield filename_node
661 minrev, maxrev = min(revs), max(revs)
661 minrev, maxrev = min(revs), max(revs)
662 for file_, node in iterfiles():
662 for file_, node in iterfiles():
663 filelog = repo.file(file_)
663 filelog = repo.file(file_)
664 # A zero count may be a directory or deleted file, so
664 # A zero count may be a directory or deleted file, so
665 # try to find matching entries on the slow path.
665 # try to find matching entries on the slow path.
666 if filelog.count() == 0:
666 if filelog.count() == 0:
667 slowpath = True
667 slowpath = True
668 break
668 break
669 for rev, copied in filerevgen(filelog, node):
669 for rev, copied in filerevgen(filelog, node):
670 if rev <= maxrev:
670 if rev <= maxrev:
671 if rev < minrev:
671 if rev < minrev:
672 break
672 break
673 fncache.setdefault(rev, [])
673 fncache.setdefault(rev, [])
674 fncache[rev].append(file_)
674 fncache[rev].append(file_)
675 wanted[rev] = 1
675 wanted[rev] = 1
676 if follow and copied:
676 if follow and copied:
677 copies.append(copied)
677 copies.append(copied)
678 if slowpath:
678 if slowpath:
679 if follow:
679 if follow:
680 raise util.Abort(_('can only follow copies/renames for explicit '
680 raise util.Abort(_('can only follow copies/renames for explicit '
681 'file names'))
681 'file names'))
682
682
683 # The slow path checks files modified in every changeset.
683 # The slow path checks files modified in every changeset.
684 def changerevgen():
684 def changerevgen():
685 for i, window in increasing_windows(repo.changelog.count()-1,
685 for i, window in increasing_windows(repo.changelog.count()-1,
686 nullrev):
686 nullrev):
687 for j in xrange(i - window, i + 1):
687 for j in xrange(i - window, i + 1):
688 yield j, change(j)[3]
688 yield j, change(j)[3]
689
689
690 for rev, changefiles in changerevgen():
690 for rev, changefiles in changerevgen():
691 matches = filter(matchfn, changefiles)
691 matches = filter(matchfn, changefiles)
692 if matches:
692 if matches:
693 fncache[rev] = matches
693 fncache[rev] = matches
694 wanted[rev] = 1
694 wanted[rev] = 1
695
695
696 class followfilter:
696 class followfilter:
697 def __init__(self, onlyfirst=False):
697 def __init__(self, onlyfirst=False):
698 self.startrev = nullrev
698 self.startrev = nullrev
699 self.roots = []
699 self.roots = []
700 self.onlyfirst = onlyfirst
700 self.onlyfirst = onlyfirst
701
701
702 def match(self, rev):
702 def match(self, rev):
703 def realparents(rev):
703 def realparents(rev):
704 if self.onlyfirst:
704 if self.onlyfirst:
705 return repo.changelog.parentrevs(rev)[0:1]
705 return repo.changelog.parentrevs(rev)[0:1]
706 else:
706 else:
707 return filter(lambda x: x != nullrev,
707 return filter(lambda x: x != nullrev,
708 repo.changelog.parentrevs(rev))
708 repo.changelog.parentrevs(rev))
709
709
710 if self.startrev == nullrev:
710 if self.startrev == nullrev:
711 self.startrev = rev
711 self.startrev = rev
712 return True
712 return True
713
713
714 if rev > self.startrev:
714 if rev > self.startrev:
715 # forward: all descendants
715 # forward: all descendants
716 if not self.roots:
716 if not self.roots:
717 self.roots.append(self.startrev)
717 self.roots.append(self.startrev)
718 for parent in realparents(rev):
718 for parent in realparents(rev):
719 if parent in self.roots:
719 if parent in self.roots:
720 self.roots.append(rev)
720 self.roots.append(rev)
721 return True
721 return True
722 else:
722 else:
723 # backwards: all parents
723 # backwards: all parents
724 if not self.roots:
724 if not self.roots:
725 self.roots.extend(realparents(self.startrev))
725 self.roots.extend(realparents(self.startrev))
726 if rev in self.roots:
726 if rev in self.roots:
727 self.roots.remove(rev)
727 self.roots.remove(rev)
728 self.roots.extend(realparents(rev))
728 self.roots.extend(realparents(rev))
729 return True
729 return True
730
730
731 return False
731 return False
732
732
733 # it might be worthwhile to do this in the iterator if the rev range
733 # it might be worthwhile to do this in the iterator if the rev range
734 # is descending and the prune args are all within that range
734 # is descending and the prune args are all within that range
735 for rev in opts.get('prune', ()):
735 for rev in opts.get('prune', ()):
736 rev = repo.changelog.rev(repo.lookup(rev))
736 rev = repo.changelog.rev(repo.lookup(rev))
737 ff = followfilter()
737 ff = followfilter()
738 stop = min(revs[0], revs[-1])
738 stop = min(revs[0], revs[-1])
739 for x in xrange(rev, stop-1, -1):
739 for x in xrange(rev, stop-1, -1):
740 if ff.match(x) and x in wanted:
740 if ff.match(x) and x in wanted:
741 del wanted[x]
741 del wanted[x]
742
742
743 def iterate():
743 def iterate():
744 if follow and not files:
744 if follow and not files:
745 ff = followfilter(onlyfirst=opts.get('follow_first'))
745 ff = followfilter(onlyfirst=opts.get('follow_first'))
746 def want(rev):
746 def want(rev):
747 if ff.match(rev) and rev in wanted:
747 if ff.match(rev) and rev in wanted:
748 return True
748 return True
749 return False
749 return False
750 else:
750 else:
751 def want(rev):
751 def want(rev):
752 return rev in wanted
752 return rev in wanted
753
753
754 for i, window in increasing_windows(0, len(revs)):
754 for i, window in increasing_windows(0, len(revs)):
755 yield 'window', revs[0] < revs[-1], revs[-1]
755 yield 'window', revs[0] < revs[-1], revs[-1]
756 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
756 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
757 srevs = list(nrevs)
757 srevs = list(nrevs)
758 srevs.sort()
758 srevs.sort()
759 for rev in srevs:
759 for rev in srevs:
760 fns = fncache.get(rev)
760 fns = fncache.get(rev)
761 if not fns:
761 if not fns:
762 def fns_generator():
762 def fns_generator():
763 for f in change(rev)[3]:
763 for f in change(rev)[3]:
764 if matchfn(f):
764 if matchfn(f):
765 yield f
765 yield f
766 fns = fns_generator()
766 fns = fns_generator()
767 yield 'add', rev, fns
767 yield 'add', rev, fns
768 for rev in nrevs:
768 for rev in nrevs:
769 yield 'iter', rev, None
769 yield 'iter', rev, None
770 return iterate(), matchfn
770 return iterate(), matchfn
@@ -1,1379 +1,1375
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import gettext as _
15 from i18n import gettext as _
16 from demandload import *
16 from demandload import *
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
19
19
20 try:
20 try:
21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
22 or "ascii"
22 or "ascii"
23 except locale.Error:
23 except locale.Error:
24 _encoding = 'ascii'
24 _encoding = 'ascii'
25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
26 _fallbackencoding = 'ISO-8859-1'
26 _fallbackencoding = 'ISO-8859-1'
27
27
28 def tolocal(s):
28 def tolocal(s):
29 """
29 """
30 Convert a string from internal UTF-8 to local encoding
30 Convert a string from internal UTF-8 to local encoding
31
31
32 All internal strings should be UTF-8 but some repos before the
32 All internal strings should be UTF-8 but some repos before the
33 implementation of locale support may contain latin1 or possibly
33 implementation of locale support may contain latin1 or possibly
34 other character sets. We attempt to decode everything strictly
34 other character sets. We attempt to decode everything strictly
35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
36 replace unknown characters.
36 replace unknown characters.
37 """
37 """
38 for e in ('UTF-8', _fallbackencoding):
38 for e in ('UTF-8', _fallbackencoding):
39 try:
39 try:
40 u = s.decode(e) # attempt strict decoding
40 u = s.decode(e) # attempt strict decoding
41 return u.encode(_encoding, "replace")
41 return u.encode(_encoding, "replace")
42 except LookupError, k:
42 except LookupError, k:
43 raise Abort(_("%s, please check your locale settings") % k)
43 raise Abort(_("%s, please check your locale settings") % k)
44 except UnicodeDecodeError:
44 except UnicodeDecodeError:
45 pass
45 pass
46 u = s.decode("utf-8", "replace") # last ditch
46 u = s.decode("utf-8", "replace") # last ditch
47 return u.encode(_encoding, "replace")
47 return u.encode(_encoding, "replace")
48
48
49 def fromlocal(s):
49 def fromlocal(s):
50 """
50 """
51 Convert a string from the local character encoding to UTF-8
51 Convert a string from the local character encoding to UTF-8
52
52
53 We attempt to decode strings using the encoding mode set by
53 We attempt to decode strings using the encoding mode set by
54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
55 characters will cause an error message. Other modes include
55 characters will cause an error message. Other modes include
56 'replace', which replaces unknown characters with a special
56 'replace', which replaces unknown characters with a special
57 Unicode character, and 'ignore', which drops the character.
57 Unicode character, and 'ignore', which drops the character.
58 """
58 """
59 try:
59 try:
60 return s.decode(_encoding, _encodingmode).encode("utf-8")
60 return s.decode(_encoding, _encodingmode).encode("utf-8")
61 except UnicodeDecodeError, inst:
61 except UnicodeDecodeError, inst:
62 sub = s[max(0, inst.start-10):inst.start+10]
62 sub = s[max(0, inst.start-10):inst.start+10]
63 raise Abort("decoding near '%s': %s!" % (sub, inst))
63 raise Abort("decoding near '%s': %s!" % (sub, inst))
64 except LookupError, k:
64 except LookupError, k:
65 raise Abort(_("%s, please check your locale settings") % k)
65 raise Abort(_("%s, please check your locale settings") % k)
66
66
67 def locallen(s):
67 def locallen(s):
68 """Find the length in characters of a local string"""
68 """Find the length in characters of a local string"""
69 return len(s.decode(_encoding, "replace"))
69 return len(s.decode(_encoding, "replace"))
70
70
71 def localsub(s, a, b=None):
71 def localsub(s, a, b=None):
72 try:
72 try:
73 u = s.decode(_encoding, _encodingmode)
73 u = s.decode(_encoding, _encodingmode)
74 if b is not None:
74 if b is not None:
75 u = u[a:b]
75 u = u[a:b]
76 else:
76 else:
77 u = u[:a]
77 u = u[:a]
78 return u.encode(_encoding, _encodingmode)
78 return u.encode(_encoding, _encodingmode)
79 except UnicodeDecodeError, inst:
79 except UnicodeDecodeError, inst:
80 sub = s[max(0, inst.start-10), inst.start+10]
80 sub = s[max(0, inst.start-10), inst.start+10]
81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%b %d %H:%M:%S %Y',
96 '%b %d %H:%M:%S %Y',
97 '%b %d %I:%M:%S%p %Y',
97 '%b %d %I:%M:%S%p %Y',
98 '%b %d %H:%M:%S',
98 '%b %d %H:%M:%S',
99 '%b %d %I:%M:%S%p',
99 '%b %d %I:%M:%S%p',
100 '%b %d %H:%M',
100 '%b %d %H:%M',
101 '%b %d %I:%M%p',
101 '%b %d %I:%M%p',
102 '%b %d %Y',
102 '%b %d %Y',
103 '%b %d',
103 '%b %d',
104 '%H:%M:%S',
104 '%H:%M:%S',
105 '%I:%M:%SP',
105 '%I:%M:%SP',
106 '%H:%M',
106 '%H:%M',
107 '%I:%M%p',
107 '%I:%M%p',
108 )
108 )
109
109
110 extendeddateformats = defaultdateformats + (
110 extendeddateformats = defaultdateformats + (
111 "%Y",
111 "%Y",
112 "%Y-%m",
112 "%Y-%m",
113 "%b",
113 "%b",
114 "%b %Y",
114 "%b %Y",
115 )
115 )
116
116
117 class SignalInterrupt(Exception):
117 class SignalInterrupt(Exception):
118 """Exception raised on SIGTERM and SIGHUP."""
118 """Exception raised on SIGTERM and SIGHUP."""
119
119
120 # like SafeConfigParser but with case-sensitive keys
120 # like SafeConfigParser but with case-sensitive keys
121 class configparser(ConfigParser.SafeConfigParser):
121 class configparser(ConfigParser.SafeConfigParser):
122 def optionxform(self, optionstr):
122 def optionxform(self, optionstr):
123 return optionstr
123 return optionstr
124
124
125 def cachefunc(func):
125 def cachefunc(func):
126 '''cache the result of function calls'''
126 '''cache the result of function calls'''
127 # XXX doesn't handle keywords args
127 # XXX doesn't handle keywords args
128 cache = {}
128 cache = {}
129 if func.func_code.co_argcount == 1:
129 if func.func_code.co_argcount == 1:
130 # we gain a small amount of time because
130 # we gain a small amount of time because
131 # we don't need to pack/unpack the list
131 # we don't need to pack/unpack the list
132 def f(arg):
132 def f(arg):
133 if arg not in cache:
133 if arg not in cache:
134 cache[arg] = func(arg)
134 cache[arg] = func(arg)
135 return cache[arg]
135 return cache[arg]
136 else:
136 else:
137 def f(*args):
137 def f(*args):
138 if args not in cache:
138 if args not in cache:
139 cache[args] = func(*args)
139 cache[args] = func(*args)
140 return cache[args]
140 return cache[args]
141
141
142 return f
142 return f
143
143
144 def pipefilter(s, cmd):
144 def pipefilter(s, cmd):
145 '''filter string S through command CMD, returning its output'''
145 '''filter string S through command CMD, returning its output'''
146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
147 def writer():
147 def writer():
148 try:
148 try:
149 pin.write(s)
149 pin.write(s)
150 pin.close()
150 pin.close()
151 except IOError, inst:
151 except IOError, inst:
152 if inst.errno != errno.EPIPE:
152 if inst.errno != errno.EPIPE:
153 raise
153 raise
154
154
155 # we should use select instead on UNIX, but this will work on most
155 # we should use select instead on UNIX, but this will work on most
156 # systems, including Windows
156 # systems, including Windows
157 w = threading.Thread(target=writer)
157 w = threading.Thread(target=writer)
158 w.start()
158 w.start()
159 f = pout.read()
159 f = pout.read()
160 pout.close()
160 pout.close()
161 w.join()
161 w.join()
162 return f
162 return f
163
163
164 def tempfilter(s, cmd):
164 def tempfilter(s, cmd):
165 '''filter string S through a pair of temporary files with CMD.
165 '''filter string S through a pair of temporary files with CMD.
166 CMD is used as a template to create the real command to be run,
166 CMD is used as a template to create the real command to be run,
167 with the strings INFILE and OUTFILE replaced by the real names of
167 with the strings INFILE and OUTFILE replaced by the real names of
168 the temporary files generated.'''
168 the temporary files generated.'''
169 inname, outname = None, None
169 inname, outname = None, None
170 try:
170 try:
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
172 fp = os.fdopen(infd, 'wb')
172 fp = os.fdopen(infd, 'wb')
173 fp.write(s)
173 fp.write(s)
174 fp.close()
174 fp.close()
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
176 os.close(outfd)
176 os.close(outfd)
177 cmd = cmd.replace('INFILE', inname)
177 cmd = cmd.replace('INFILE', inname)
178 cmd = cmd.replace('OUTFILE', outname)
178 cmd = cmd.replace('OUTFILE', outname)
179 code = os.system(cmd)
179 code = os.system(cmd)
180 if code: raise Abort(_("command '%s' failed: %s") %
180 if code: raise Abort(_("command '%s' failed: %s") %
181 (cmd, explain_exit(code)))
181 (cmd, explain_exit(code)))
182 return open(outname, 'rb').read()
182 return open(outname, 'rb').read()
183 finally:
183 finally:
184 try:
184 try:
185 if inname: os.unlink(inname)
185 if inname: os.unlink(inname)
186 except: pass
186 except: pass
187 try:
187 try:
188 if outname: os.unlink(outname)
188 if outname: os.unlink(outname)
189 except: pass
189 except: pass
190
190
191 filtertable = {
191 filtertable = {
192 'tempfile:': tempfilter,
192 'tempfile:': tempfilter,
193 'pipe:': pipefilter,
193 'pipe:': pipefilter,
194 }
194 }
195
195
196 def filter(s, cmd):
196 def filter(s, cmd):
197 "filter a string through a command that transforms its input to its output"
197 "filter a string through a command that transforms its input to its output"
198 for name, fn in filtertable.iteritems():
198 for name, fn in filtertable.iteritems():
199 if cmd.startswith(name):
199 if cmd.startswith(name):
200 return fn(s, cmd[len(name):].lstrip())
200 return fn(s, cmd[len(name):].lstrip())
201 return pipefilter(s, cmd)
201 return pipefilter(s, cmd)
202
202
203 def find_in_path(name, path, default=None):
203 def find_in_path(name, path, default=None):
204 '''find name in search path. path can be string (will be split
204 '''find name in search path. path can be string (will be split
205 with os.pathsep), or iterable thing that returns strings. if name
205 with os.pathsep), or iterable thing that returns strings. if name
206 found, return path to name. else return default.'''
206 found, return path to name. else return default.'''
207 if isinstance(path, str):
207 if isinstance(path, str):
208 path = path.split(os.pathsep)
208 path = path.split(os.pathsep)
209 for p in path:
209 for p in path:
210 p_name = os.path.join(p, name)
210 p_name = os.path.join(p, name)
211 if os.path.exists(p_name):
211 if os.path.exists(p_name):
212 return p_name
212 return p_name
213 return default
213 return default
214
214
215 def binary(s):
215 def binary(s):
216 """return true if a string is binary data using diff's heuristic"""
216 """return true if a string is binary data using diff's heuristic"""
217 if s and '\0' in s[:4096]:
217 if s and '\0' in s[:4096]:
218 return True
218 return True
219 return False
219 return False
220
220
221 def unique(g):
221 def unique(g):
222 """return the uniq elements of iterable g"""
222 """return the uniq elements of iterable g"""
223 seen = {}
223 seen = {}
224 l = []
224 l = []
225 for f in g:
225 for f in g:
226 if f not in seen:
226 if f not in seen:
227 seen[f] = 1
227 seen[f] = 1
228 l.append(f)
228 l.append(f)
229 return l
229 return l
230
230
231 class Abort(Exception):
231 class Abort(Exception):
232 """Raised if a command needs to print an error and exit."""
232 """Raised if a command needs to print an error and exit."""
233
233
234 class UnexpectedOutput(Abort):
234 class UnexpectedOutput(Abort):
235 """Raised to print an error with part of output and exit."""
235 """Raised to print an error with part of output and exit."""
236
236
237 def always(fn): return True
237 def always(fn): return True
238 def never(fn): return False
238 def never(fn): return False
239
239
240 def expand_glob(pats):
240 def expand_glob(pats):
241 '''On Windows, expand the implicit globs in a list of patterns'''
241 '''On Windows, expand the implicit globs in a list of patterns'''
242 if os.name != 'nt':
242 if os.name != 'nt':
243 return list(pats)
243 return list(pats)
244 ret = []
244 ret = []
245 for p in pats:
245 for p in pats:
246 kind, name = patkind(p, None)
246 kind, name = patkind(p, None)
247 if kind is None:
247 if kind is None:
248 globbed = glob.glob(name)
248 globbed = glob.glob(name)
249 if globbed:
249 if globbed:
250 ret.extend(globbed)
250 ret.extend(globbed)
251 continue
251 continue
252 # if we couldn't expand the glob, just keep it around
252 # if we couldn't expand the glob, just keep it around
253 ret.append(p)
253 ret.append(p)
254 return ret
254 return ret
255
255
256 def patkind(name, dflt_pat='glob'):
256 def patkind(name, dflt_pat='glob'):
257 """Split a string into an optional pattern kind prefix and the
257 """Split a string into an optional pattern kind prefix and the
258 actual pattern."""
258 actual pattern."""
259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
260 if name.startswith(prefix + ':'): return name.split(':', 1)
260 if name.startswith(prefix + ':'): return name.split(':', 1)
261 return dflt_pat, name
261 return dflt_pat, name
262
262
263 def globre(pat, head='^', tail='$'):
263 def globre(pat, head='^', tail='$'):
264 "convert a glob pattern into a regexp"
264 "convert a glob pattern into a regexp"
265 i, n = 0, len(pat)
265 i, n = 0, len(pat)
266 res = ''
266 res = ''
267 group = False
267 group = False
268 def peek(): return i < n and pat[i]
268 def peek(): return i < n and pat[i]
269 while i < n:
269 while i < n:
270 c = pat[i]
270 c = pat[i]
271 i = i+1
271 i = i+1
272 if c == '*':
272 if c == '*':
273 if peek() == '*':
273 if peek() == '*':
274 i += 1
274 i += 1
275 res += '.*'
275 res += '.*'
276 else:
276 else:
277 res += '[^/]*'
277 res += '[^/]*'
278 elif c == '?':
278 elif c == '?':
279 res += '.'
279 res += '.'
280 elif c == '[':
280 elif c == '[':
281 j = i
281 j = i
282 if j < n and pat[j] in '!]':
282 if j < n and pat[j] in '!]':
283 j += 1
283 j += 1
284 while j < n and pat[j] != ']':
284 while j < n and pat[j] != ']':
285 j += 1
285 j += 1
286 if j >= n:
286 if j >= n:
287 res += '\\['
287 res += '\\['
288 else:
288 else:
289 stuff = pat[i:j].replace('\\','\\\\')
289 stuff = pat[i:j].replace('\\','\\\\')
290 i = j + 1
290 i = j + 1
291 if stuff[0] == '!':
291 if stuff[0] == '!':
292 stuff = '^' + stuff[1:]
292 stuff = '^' + stuff[1:]
293 elif stuff[0] == '^':
293 elif stuff[0] == '^':
294 stuff = '\\' + stuff
294 stuff = '\\' + stuff
295 res = '%s[%s]' % (res, stuff)
295 res = '%s[%s]' % (res, stuff)
296 elif c == '{':
296 elif c == '{':
297 group = True
297 group = True
298 res += '(?:'
298 res += '(?:'
299 elif c == '}' and group:
299 elif c == '}' and group:
300 res += ')'
300 res += ')'
301 group = False
301 group = False
302 elif c == ',' and group:
302 elif c == ',' and group:
303 res += '|'
303 res += '|'
304 elif c == '\\':
304 elif c == '\\':
305 p = peek()
305 p = peek()
306 if p:
306 if p:
307 i += 1
307 i += 1
308 res += re.escape(p)
308 res += re.escape(p)
309 else:
309 else:
310 res += re.escape(c)
310 res += re.escape(c)
311 else:
311 else:
312 res += re.escape(c)
312 res += re.escape(c)
313 return head + res + tail
313 return head + res + tail
314
314
315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
316
316
317 def pathto(n1, n2):
317 def pathto(n1, n2):
318 '''return the relative path from one place to another.
318 '''return the relative path from one place to another.
319 n1 should use os.sep to separate directories
319 n1 should use os.sep to separate directories
320 n2 should use "/" to separate directories
320 n2 should use "/" to separate directories
321 returns an os.sep-separated path.
321 returns an os.sep-separated path.
322 '''
322 '''
323 if not n1: return localpath(n2)
323 if not n1: return localpath(n2)
324 a, b = n1.split(os.sep), n2.split('/')
324 a, b = n1.split(os.sep), n2.split('/')
325 a.reverse()
325 a.reverse()
326 b.reverse()
326 b.reverse()
327 while a and b and a[-1] == b[-1]:
327 while a and b and a[-1] == b[-1]:
328 a.pop()
328 a.pop()
329 b.pop()
329 b.pop()
330 b.reverse()
330 b.reverse()
331 return os.sep.join((['..'] * len(a)) + b)
331 return os.sep.join((['..'] * len(a)) + b)
332
332
333 def canonpath(root, cwd, myname):
333 def canonpath(root, cwd, myname):
334 """return the canonical path of myname, given cwd and root"""
334 """return the canonical path of myname, given cwd and root"""
335 if root == os.sep:
335 if root == os.sep:
336 rootsep = os.sep
336 rootsep = os.sep
337 elif root.endswith(os.sep):
337 elif root.endswith(os.sep):
338 rootsep = root
338 rootsep = root
339 else:
339 else:
340 rootsep = root + os.sep
340 rootsep = root + os.sep
341 name = myname
341 name = myname
342 if not os.path.isabs(name):
342 if not os.path.isabs(name):
343 name = os.path.join(root, cwd, name)
343 name = os.path.join(root, cwd, name)
344 name = os.path.normpath(name)
344 name = os.path.normpath(name)
345 if name != rootsep and name.startswith(rootsep):
345 if name != rootsep and name.startswith(rootsep):
346 name = name[len(rootsep):]
346 name = name[len(rootsep):]
347 audit_path(name)
347 audit_path(name)
348 return pconvert(name)
348 return pconvert(name)
349 elif name == root:
349 elif name == root:
350 return ''
350 return ''
351 else:
351 else:
352 # Determine whether `name' is in the hierarchy at or beneath `root',
352 # Determine whether `name' is in the hierarchy at or beneath `root',
353 # by iterating name=dirname(name) until that causes no change (can't
353 # by iterating name=dirname(name) until that causes no change (can't
354 # check name == '/', because that doesn't work on windows). For each
354 # check name == '/', because that doesn't work on windows). For each
355 # `name', compare dev/inode numbers. If they match, the list `rel'
355 # `name', compare dev/inode numbers. If they match, the list `rel'
356 # holds the reversed list of components making up the relative file
356 # holds the reversed list of components making up the relative file
357 # name we want.
357 # name we want.
358 root_st = os.stat(root)
358 root_st = os.stat(root)
359 rel = []
359 rel = []
360 while True:
360 while True:
361 try:
361 try:
362 name_st = os.stat(name)
362 name_st = os.stat(name)
363 except OSError:
363 except OSError:
364 break
364 break
365 if samestat(name_st, root_st):
365 if samestat(name_st, root_st):
366 if not rel:
366 if not rel:
367 # name was actually the same as root (maybe a symlink)
367 # name was actually the same as root (maybe a symlink)
368 return ''
368 return ''
369 rel.reverse()
369 rel.reverse()
370 name = os.path.join(*rel)
370 name = os.path.join(*rel)
371 audit_path(name)
371 audit_path(name)
372 return pconvert(name)
372 return pconvert(name)
373 dirname, basename = os.path.split(name)
373 dirname, basename = os.path.split(name)
374 rel.append(basename)
374 rel.append(basename)
375 if dirname == name:
375 if dirname == name:
376 break
376 break
377 name = dirname
377 name = dirname
378
378
379 raise Abort('%s not under root' % myname)
379 raise Abort('%s not under root' % myname)
380
380
381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], head='', src=None):
381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
382 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
382 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
383
383
384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], head='',
384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
385 src=None, globbed=False, default=None):
385 globbed=False, default=None):
386 default = default or 'relpath'
386 default = default or 'relpath'
387 if default == 'relpath' and not globbed:
387 if default == 'relpath' and not globbed:
388 names = expand_glob(names)
388 names = expand_glob(names)
389 return _matcher(canonroot, cwd, names, inc, exc, head, default, src)
389 return _matcher(canonroot, cwd, names, inc, exc, default, src)
390
390
391 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
391 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
392 """build a function to match a set of file patterns
392 """build a function to match a set of file patterns
393
393
394 arguments:
394 arguments:
395 canonroot - the canonical root of the tree you're matching against
395 canonroot - the canonical root of the tree you're matching against
396 cwd - the current working directory, if relevant
396 cwd - the current working directory, if relevant
397 names - patterns to find
397 names - patterns to find
398 inc - patterns to include
398 inc - patterns to include
399 exc - patterns to exclude
399 exc - patterns to exclude
400 head - a regex to prepend to patterns to control whether a match is rooted
401 dflt_pat - if a pattern in names has no explicit type, assume this one
400 dflt_pat - if a pattern in names has no explicit type, assume this one
402 src - where these patterns came from (e.g. .hgignore)
401 src - where these patterns came from (e.g. .hgignore)
403
402
404 a pattern is one of:
403 a pattern is one of:
405 'glob:<glob>' - a glob relative to cwd
404 'glob:<glob>' - a glob relative to cwd
406 're:<regexp>' - a regular expression
405 're:<regexp>' - a regular expression
407 'path:<path>' - a path relative to canonroot
406 'path:<path>' - a path relative to canonroot
408 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
409 'relpath:<path>' - a path relative to cwd
408 'relpath:<path>' - a path relative to cwd
410 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
411 '<something>' - one of the cases above, selected by the dflt_pat argument
410 '<something>' - one of the cases above, selected by the dflt_pat argument
412
411
413 returns:
412 returns:
414 a 3-tuple containing
413 a 3-tuple containing
415 - list of roots (places where one should start a recursive walk of the fs);
414 - list of roots (places where one should start a recursive walk of the fs);
416 this often matches the explicit non-pattern names passed in, but also
415 this often matches the explicit non-pattern names passed in, but also
417 includes the initial part of glob: patterns that has no glob characters
416 includes the initial part of glob: patterns that has no glob characters
418 - a bool match(filename) function
417 - a bool match(filename) function
419 - a bool indicating if any patterns were passed in
418 - a bool indicating if any patterns were passed in
420
421 todo:
422 make head regex a rooted bool
423 """
419 """
424
420
425 def contains_glob(name):
421 def contains_glob(name):
426 for c in name:
422 for c in name:
427 if c in _globchars: return True
423 if c in _globchars: return True
428 return False
424 return False
429
425
430 def regex(kind, name, tail):
426 def regex(kind, name, tail):
431 '''convert a pattern into a regular expression'''
427 '''convert a pattern into a regular expression'''
432 if not name:
428 if not name:
433 return ''
429 return ''
434 if kind == 're':
430 if kind == 're':
435 return name
431 return name
436 elif kind == 'path':
432 elif kind == 'path':
437 return '^' + re.escape(name) + '(?:/|$)'
433 return '^' + re.escape(name) + '(?:/|$)'
438 elif kind == 'relglob':
434 elif kind == 'relglob':
439 return head + globre(name, '(?:|.*/)', '(?:/|$)')
435 return globre(name, '(?:|.*/)', '(?:/|$)')
440 elif kind == 'relpath':
436 elif kind == 'relpath':
441 return head + re.escape(name) + '(?:/|$)'
437 return re.escape(name) + '(?:/|$)'
442 elif kind == 'relre':
438 elif kind == 'relre':
443 if name.startswith('^'):
439 if name.startswith('^'):
444 return name
440 return name
445 return '.*' + name
441 return '.*' + name
446 return head + globre(name, '', tail)
442 return globre(name, '', tail)
447
443
448 def matchfn(pats, tail):
444 def matchfn(pats, tail):
449 """build a matching function from a set of patterns"""
445 """build a matching function from a set of patterns"""
450 if not pats:
446 if not pats:
451 return
447 return
452 matches = []
448 matches = []
453 for k, p in pats:
449 for k, p in pats:
454 try:
450 try:
455 pat = '(?:%s)' % regex(k, p, tail)
451 pat = '(?:%s)' % regex(k, p, tail)
456 matches.append(re.compile(pat).match)
452 matches.append(re.compile(pat).match)
457 except re.error:
453 except re.error:
458 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
454 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
459 else: raise Abort("invalid pattern (%s): %s" % (k, p))
455 else: raise Abort("invalid pattern (%s): %s" % (k, p))
460
456
461 def buildfn(text):
457 def buildfn(text):
462 for m in matches:
458 for m in matches:
463 r = m(text)
459 r = m(text)
464 if r:
460 if r:
465 return r
461 return r
466
462
467 return buildfn
463 return buildfn
468
464
469 def globprefix(pat):
465 def globprefix(pat):
470 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
466 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
471 root = []
467 root = []
472 for p in pat.split('/'):
468 for p in pat.split('/'):
473 if contains_glob(p): break
469 if contains_glob(p): break
474 root.append(p)
470 root.append(p)
475 return '/'.join(root) or '.'
471 return '/'.join(root) or '.'
476
472
477 def normalizepats(names, default):
473 def normalizepats(names, default):
478 pats = []
474 pats = []
479 files = []
475 files = []
480 roots = []
476 roots = []
481 anypats = False
477 anypats = False
482 for kind, name in [patkind(p, default) for p in names]:
478 for kind, name in [patkind(p, default) for p in names]:
483 if kind in ('glob', 'relpath'):
479 if kind in ('glob', 'relpath'):
484 name = canonpath(canonroot, cwd, name)
480 name = canonpath(canonroot, cwd, name)
485 elif kind in ('relglob', 'path'):
481 elif kind in ('relglob', 'path'):
486 name = normpath(name)
482 name = normpath(name)
487 if kind in ('glob', 're', 'relglob', 'relre'):
483 if kind in ('glob', 're', 'relglob', 'relre'):
488 pats.append((kind, name))
484 pats.append((kind, name))
489 anypats = True
485 anypats = True
490 if kind == 'glob':
486 if kind == 'glob':
491 root = globprefix(name)
487 root = globprefix(name)
492 roots.append(root)
488 roots.append(root)
493 elif kind in ('relpath', 'path'):
489 elif kind in ('relpath', 'path'):
494 files.append((kind, name))
490 files.append((kind, name))
495 roots.append(name)
491 roots.append(name)
496 elif kind == 'relglob':
492 elif kind == 'relglob':
497 roots.append('.')
493 roots.append('.')
498 return roots, pats + files, anypats
494 return roots, pats + files, anypats
499
495
500 roots, pats, anypats = normalizepats(names, dflt_pat)
496 roots, pats, anypats = normalizepats(names, dflt_pat)
501
497
502 patmatch = matchfn(pats, '$') or always
498 patmatch = matchfn(pats, '$') or always
503 incmatch = always
499 incmatch = always
504 if inc:
500 if inc:
505 dummy, inckinds, dummy = normalizepats(inc, 'glob')
501 dummy, inckinds, dummy = normalizepats(inc, 'glob')
506 incmatch = matchfn(inckinds, '(?:/|$)')
502 incmatch = matchfn(inckinds, '(?:/|$)')
507 excmatch = lambda fn: False
503 excmatch = lambda fn: False
508 if exc:
504 if exc:
509 dummy, exckinds, dummy = normalizepats(exc, 'glob')
505 dummy, exckinds, dummy = normalizepats(exc, 'glob')
510 excmatch = matchfn(exckinds, '(?:/|$)')
506 excmatch = matchfn(exckinds, '(?:/|$)')
511
507
512 return (roots,
508 return (roots,
513 lambda fn: (incmatch(fn) and not excmatch(fn) and patmatch(fn)),
509 lambda fn: (incmatch(fn) and not excmatch(fn) and patmatch(fn)),
514 (inc or exc or anypats) and True)
510 (inc or exc or anypats) and True)
515
511
516 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
512 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
517 '''enhanced shell command execution.
513 '''enhanced shell command execution.
518 run with environment maybe modified, maybe in different dir.
514 run with environment maybe modified, maybe in different dir.
519
515
520 if command fails and onerr is None, return status. if ui object,
516 if command fails and onerr is None, return status. if ui object,
521 print error message and return status, else raise onerr object as
517 print error message and return status, else raise onerr object as
522 exception.'''
518 exception.'''
523 def py2shell(val):
519 def py2shell(val):
524 'convert python object into string that is useful to shell'
520 'convert python object into string that is useful to shell'
525 if val in (None, False):
521 if val in (None, False):
526 return '0'
522 return '0'
527 if val == True:
523 if val == True:
528 return '1'
524 return '1'
529 return str(val)
525 return str(val)
530 oldenv = {}
526 oldenv = {}
531 for k in environ:
527 for k in environ:
532 oldenv[k] = os.environ.get(k)
528 oldenv[k] = os.environ.get(k)
533 if cwd is not None:
529 if cwd is not None:
534 oldcwd = os.getcwd()
530 oldcwd = os.getcwd()
535 origcmd = cmd
531 origcmd = cmd
536 if os.name == 'nt':
532 if os.name == 'nt':
537 cmd = '"%s"' % cmd
533 cmd = '"%s"' % cmd
538 try:
534 try:
539 for k, v in environ.iteritems():
535 for k, v in environ.iteritems():
540 os.environ[k] = py2shell(v)
536 os.environ[k] = py2shell(v)
541 if cwd is not None and oldcwd != cwd:
537 if cwd is not None and oldcwd != cwd:
542 os.chdir(cwd)
538 os.chdir(cwd)
543 rc = os.system(cmd)
539 rc = os.system(cmd)
544 if rc and onerr:
540 if rc and onerr:
545 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
541 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
546 explain_exit(rc)[0])
542 explain_exit(rc)[0])
547 if errprefix:
543 if errprefix:
548 errmsg = '%s: %s' % (errprefix, errmsg)
544 errmsg = '%s: %s' % (errprefix, errmsg)
549 try:
545 try:
550 onerr.warn(errmsg + '\n')
546 onerr.warn(errmsg + '\n')
551 except AttributeError:
547 except AttributeError:
552 raise onerr(errmsg)
548 raise onerr(errmsg)
553 return rc
549 return rc
554 finally:
550 finally:
555 for k, v in oldenv.iteritems():
551 for k, v in oldenv.iteritems():
556 if v is None:
552 if v is None:
557 del os.environ[k]
553 del os.environ[k]
558 else:
554 else:
559 os.environ[k] = v
555 os.environ[k] = v
560 if cwd is not None and oldcwd != cwd:
556 if cwd is not None and oldcwd != cwd:
561 os.chdir(oldcwd)
557 os.chdir(oldcwd)
562
558
563 def rename(src, dst):
559 def rename(src, dst):
564 """forcibly rename a file"""
560 """forcibly rename a file"""
565 try:
561 try:
566 os.rename(src, dst)
562 os.rename(src, dst)
567 except OSError, err:
563 except OSError, err:
568 # on windows, rename to existing file is not allowed, so we
564 # on windows, rename to existing file is not allowed, so we
569 # must delete destination first. but if file is open, unlink
565 # must delete destination first. but if file is open, unlink
570 # schedules it for delete but does not delete it. rename
566 # schedules it for delete but does not delete it. rename
571 # happens immediately even for open files, so we create
567 # happens immediately even for open files, so we create
572 # temporary file, delete it, rename destination to that name,
568 # temporary file, delete it, rename destination to that name,
573 # then delete that. then rename is safe to do.
569 # then delete that. then rename is safe to do.
574 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
570 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
575 os.close(fd)
571 os.close(fd)
576 os.unlink(temp)
572 os.unlink(temp)
577 os.rename(dst, temp)
573 os.rename(dst, temp)
578 os.unlink(temp)
574 os.unlink(temp)
579 os.rename(src, dst)
575 os.rename(src, dst)
580
576
581 def unlink(f):
577 def unlink(f):
582 """unlink and remove the directory if it is empty"""
578 """unlink and remove the directory if it is empty"""
583 os.unlink(f)
579 os.unlink(f)
584 # try removing directories that might now be empty
580 # try removing directories that might now be empty
585 try:
581 try:
586 os.removedirs(os.path.dirname(f))
582 os.removedirs(os.path.dirname(f))
587 except OSError:
583 except OSError:
588 pass
584 pass
589
585
590 def copyfile(src, dest):
586 def copyfile(src, dest):
591 "copy a file, preserving mode"
587 "copy a file, preserving mode"
592 try:
588 try:
593 shutil.copyfile(src, dest)
589 shutil.copyfile(src, dest)
594 shutil.copymode(src, dest)
590 shutil.copymode(src, dest)
595 except shutil.Error, inst:
591 except shutil.Error, inst:
596 raise Abort(str(inst))
592 raise Abort(str(inst))
597
593
598 def copyfiles(src, dst, hardlink=None):
594 def copyfiles(src, dst, hardlink=None):
599 """Copy a directory tree using hardlinks if possible"""
595 """Copy a directory tree using hardlinks if possible"""
600
596
601 if hardlink is None:
597 if hardlink is None:
602 hardlink = (os.stat(src).st_dev ==
598 hardlink = (os.stat(src).st_dev ==
603 os.stat(os.path.dirname(dst)).st_dev)
599 os.stat(os.path.dirname(dst)).st_dev)
604
600
605 if os.path.isdir(src):
601 if os.path.isdir(src):
606 os.mkdir(dst)
602 os.mkdir(dst)
607 for name in os.listdir(src):
603 for name in os.listdir(src):
608 srcname = os.path.join(src, name)
604 srcname = os.path.join(src, name)
609 dstname = os.path.join(dst, name)
605 dstname = os.path.join(dst, name)
610 copyfiles(srcname, dstname, hardlink)
606 copyfiles(srcname, dstname, hardlink)
611 else:
607 else:
612 if hardlink:
608 if hardlink:
613 try:
609 try:
614 os_link(src, dst)
610 os_link(src, dst)
615 except (IOError, OSError):
611 except (IOError, OSError):
616 hardlink = False
612 hardlink = False
617 shutil.copy(src, dst)
613 shutil.copy(src, dst)
618 else:
614 else:
619 shutil.copy(src, dst)
615 shutil.copy(src, dst)
620
616
621 def audit_path(path):
617 def audit_path(path):
622 """Abort if path contains dangerous components"""
618 """Abort if path contains dangerous components"""
623 parts = os.path.normcase(path).split(os.sep)
619 parts = os.path.normcase(path).split(os.sep)
624 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
620 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
625 or os.pardir in parts):
621 or os.pardir in parts):
626 raise Abort(_("path contains illegal component: %s\n") % path)
622 raise Abort(_("path contains illegal component: %s\n") % path)
627
623
628 def _makelock_file(info, pathname):
624 def _makelock_file(info, pathname):
629 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
625 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
630 os.write(ld, info)
626 os.write(ld, info)
631 os.close(ld)
627 os.close(ld)
632
628
633 def _readlock_file(pathname):
629 def _readlock_file(pathname):
634 return posixfile(pathname).read()
630 return posixfile(pathname).read()
635
631
636 def nlinks(pathname):
632 def nlinks(pathname):
637 """Return number of hardlinks for the given file."""
633 """Return number of hardlinks for the given file."""
638 return os.lstat(pathname).st_nlink
634 return os.lstat(pathname).st_nlink
639
635
640 if hasattr(os, 'link'):
636 if hasattr(os, 'link'):
641 os_link = os.link
637 os_link = os.link
642 else:
638 else:
643 def os_link(src, dst):
639 def os_link(src, dst):
644 raise OSError(0, _("Hardlinks not supported"))
640 raise OSError(0, _("Hardlinks not supported"))
645
641
646 def fstat(fp):
642 def fstat(fp):
647 '''stat file object that may not have fileno method.'''
643 '''stat file object that may not have fileno method.'''
648 try:
644 try:
649 return os.fstat(fp.fileno())
645 return os.fstat(fp.fileno())
650 except AttributeError:
646 except AttributeError:
651 return os.stat(fp.name)
647 return os.stat(fp.name)
652
648
653 posixfile = file
649 posixfile = file
654
650
655 def is_win_9x():
651 def is_win_9x():
656 '''return true if run on windows 95, 98 or me.'''
652 '''return true if run on windows 95, 98 or me.'''
657 try:
653 try:
658 return sys.getwindowsversion()[3] == 1
654 return sys.getwindowsversion()[3] == 1
659 except AttributeError:
655 except AttributeError:
660 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
656 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
661
657
662 getuser_fallback = None
658 getuser_fallback = None
663
659
664 def getuser():
660 def getuser():
665 '''return name of current user'''
661 '''return name of current user'''
666 try:
662 try:
667 return getpass.getuser()
663 return getpass.getuser()
668 except ImportError:
664 except ImportError:
669 # import of pwd will fail on windows - try fallback
665 # import of pwd will fail on windows - try fallback
670 if getuser_fallback:
666 if getuser_fallback:
671 return getuser_fallback()
667 return getuser_fallback()
672 # raised if win32api not available
668 # raised if win32api not available
673 raise Abort(_('user name not available - set USERNAME '
669 raise Abort(_('user name not available - set USERNAME '
674 'environment variable'))
670 'environment variable'))
675
671
676 def username(uid=None):
672 def username(uid=None):
677 """Return the name of the user with the given uid.
673 """Return the name of the user with the given uid.
678
674
679 If uid is None, return the name of the current user."""
675 If uid is None, return the name of the current user."""
680 try:
676 try:
681 import pwd
677 import pwd
682 if uid is None:
678 if uid is None:
683 uid = os.getuid()
679 uid = os.getuid()
684 try:
680 try:
685 return pwd.getpwuid(uid)[0]
681 return pwd.getpwuid(uid)[0]
686 except KeyError:
682 except KeyError:
687 return str(uid)
683 return str(uid)
688 except ImportError:
684 except ImportError:
689 return None
685 return None
690
686
691 def groupname(gid=None):
687 def groupname(gid=None):
692 """Return the name of the group with the given gid.
688 """Return the name of the group with the given gid.
693
689
694 If gid is None, return the name of the current group."""
690 If gid is None, return the name of the current group."""
695 try:
691 try:
696 import grp
692 import grp
697 if gid is None:
693 if gid is None:
698 gid = os.getgid()
694 gid = os.getgid()
699 try:
695 try:
700 return grp.getgrgid(gid)[0]
696 return grp.getgrgid(gid)[0]
701 except KeyError:
697 except KeyError:
702 return str(gid)
698 return str(gid)
703 except ImportError:
699 except ImportError:
704 return None
700 return None
705
701
706 # File system features
702 # File system features
707
703
708 def checkfolding(path):
704 def checkfolding(path):
709 """
705 """
710 Check whether the given path is on a case-sensitive filesystem
706 Check whether the given path is on a case-sensitive filesystem
711
707
712 Requires a path (like /foo/.hg) ending with a foldable final
708 Requires a path (like /foo/.hg) ending with a foldable final
713 directory component.
709 directory component.
714 """
710 """
715 s1 = os.stat(path)
711 s1 = os.stat(path)
716 d, b = os.path.split(path)
712 d, b = os.path.split(path)
717 p2 = os.path.join(d, b.upper())
713 p2 = os.path.join(d, b.upper())
718 if path == p2:
714 if path == p2:
719 p2 = os.path.join(d, b.lower())
715 p2 = os.path.join(d, b.lower())
720 try:
716 try:
721 s2 = os.stat(p2)
717 s2 = os.stat(p2)
722 if s2 == s1:
718 if s2 == s1:
723 return False
719 return False
724 return True
720 return True
725 except:
721 except:
726 return True
722 return True
727
723
728 # Platform specific variants
724 # Platform specific variants
729 if os.name == 'nt':
725 if os.name == 'nt':
730 demandload(globals(), "msvcrt")
726 demandload(globals(), "msvcrt")
731 nulldev = 'NUL:'
727 nulldev = 'NUL:'
732
728
733 class winstdout:
729 class winstdout:
734 '''stdout on windows misbehaves if sent through a pipe'''
730 '''stdout on windows misbehaves if sent through a pipe'''
735
731
736 def __init__(self, fp):
732 def __init__(self, fp):
737 self.fp = fp
733 self.fp = fp
738
734
739 def __getattr__(self, key):
735 def __getattr__(self, key):
740 return getattr(self.fp, key)
736 return getattr(self.fp, key)
741
737
742 def close(self):
738 def close(self):
743 try:
739 try:
744 self.fp.close()
740 self.fp.close()
745 except: pass
741 except: pass
746
742
747 def write(self, s):
743 def write(self, s):
748 try:
744 try:
749 return self.fp.write(s)
745 return self.fp.write(s)
750 except IOError, inst:
746 except IOError, inst:
751 if inst.errno != 0: raise
747 if inst.errno != 0: raise
752 self.close()
748 self.close()
753 raise IOError(errno.EPIPE, 'Broken pipe')
749 raise IOError(errno.EPIPE, 'Broken pipe')
754
750
755 def flush(self):
751 def flush(self):
756 try:
752 try:
757 return self.fp.flush()
753 return self.fp.flush()
758 except IOError, inst:
754 except IOError, inst:
759 if inst.errno != errno.EINVAL: raise
755 if inst.errno != errno.EINVAL: raise
760 self.close()
756 self.close()
761 raise IOError(errno.EPIPE, 'Broken pipe')
757 raise IOError(errno.EPIPE, 'Broken pipe')
762
758
763 sys.stdout = winstdout(sys.stdout)
759 sys.stdout = winstdout(sys.stdout)
764
760
765 def system_rcpath():
761 def system_rcpath():
766 try:
762 try:
767 return system_rcpath_win32()
763 return system_rcpath_win32()
768 except:
764 except:
769 return [r'c:\mercurial\mercurial.ini']
765 return [r'c:\mercurial\mercurial.ini']
770
766
771 def os_rcpath():
767 def os_rcpath():
772 '''return default os-specific hgrc search path'''
768 '''return default os-specific hgrc search path'''
773 path = system_rcpath()
769 path = system_rcpath()
774 path.append(user_rcpath())
770 path.append(user_rcpath())
775 userprofile = os.environ.get('USERPROFILE')
771 userprofile = os.environ.get('USERPROFILE')
776 if userprofile:
772 if userprofile:
777 path.append(os.path.join(userprofile, 'mercurial.ini'))
773 path.append(os.path.join(userprofile, 'mercurial.ini'))
778 return path
774 return path
779
775
780 def user_rcpath():
776 def user_rcpath():
781 '''return os-specific hgrc search path to the user dir'''
777 '''return os-specific hgrc search path to the user dir'''
782 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
778 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
783
779
784 def parse_patch_output(output_line):
780 def parse_patch_output(output_line):
785 """parses the output produced by patch and returns the file name"""
781 """parses the output produced by patch and returns the file name"""
786 pf = output_line[14:]
782 pf = output_line[14:]
787 if pf[0] == '`':
783 if pf[0] == '`':
788 pf = pf[1:-1] # Remove the quotes
784 pf = pf[1:-1] # Remove the quotes
789 return pf
785 return pf
790
786
791 def testpid(pid):
787 def testpid(pid):
792 '''return False if pid dead, True if running or not known'''
788 '''return False if pid dead, True if running or not known'''
793 return True
789 return True
794
790
795 def is_exec(f, last):
791 def is_exec(f, last):
796 return last
792 return last
797
793
798 def set_exec(f, mode):
794 def set_exec(f, mode):
799 pass
795 pass
800
796
801 def set_binary(fd):
797 def set_binary(fd):
802 msvcrt.setmode(fd.fileno(), os.O_BINARY)
798 msvcrt.setmode(fd.fileno(), os.O_BINARY)
803
799
804 def pconvert(path):
800 def pconvert(path):
805 return path.replace("\\", "/")
801 return path.replace("\\", "/")
806
802
807 def localpath(path):
803 def localpath(path):
808 return path.replace('/', '\\')
804 return path.replace('/', '\\')
809
805
810 def normpath(path):
806 def normpath(path):
811 return pconvert(os.path.normpath(path))
807 return pconvert(os.path.normpath(path))
812
808
813 makelock = _makelock_file
809 makelock = _makelock_file
814 readlock = _readlock_file
810 readlock = _readlock_file
815
811
816 def samestat(s1, s2):
812 def samestat(s1, s2):
817 return False
813 return False
818
814
819 # A sequence of backslashes is special iff it precedes a double quote:
815 # A sequence of backslashes is special iff it precedes a double quote:
820 # - if there's an even number of backslashes, the double quote is not
816 # - if there's an even number of backslashes, the double quote is not
821 # quoted (i.e. it ends the quoted region)
817 # quoted (i.e. it ends the quoted region)
822 # - if there's an odd number of backslashes, the double quote is quoted
818 # - if there's an odd number of backslashes, the double quote is quoted
823 # - in both cases, every pair of backslashes is unquoted into a single
819 # - in both cases, every pair of backslashes is unquoted into a single
824 # backslash
820 # backslash
825 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
821 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
826 # So, to quote a string, we must surround it in double quotes, double
822 # So, to quote a string, we must surround it in double quotes, double
827 # the number of backslashes that preceed double quotes and add another
823 # the number of backslashes that preceed double quotes and add another
828 # backslash before every double quote (being careful with the double
824 # backslash before every double quote (being careful with the double
829 # quote we've appended to the end)
825 # quote we've appended to the end)
830 _quotere = None
826 _quotere = None
831 def shellquote(s):
827 def shellquote(s):
832 global _quotere
828 global _quotere
833 if _quotere is None:
829 if _quotere is None:
834 _quotere = re.compile(r'(\\*)("|\\$)')
830 _quotere = re.compile(r'(\\*)("|\\$)')
835 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
831 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
836
832
837 def explain_exit(code):
833 def explain_exit(code):
838 return _("exited with status %d") % code, code
834 return _("exited with status %d") % code, code
839
835
840 # if you change this stub into a real check, please try to implement the
836 # if you change this stub into a real check, please try to implement the
841 # username and groupname functions above, too.
837 # username and groupname functions above, too.
842 def isowner(fp, st=None):
838 def isowner(fp, st=None):
843 return True
839 return True
844
840
845 try:
841 try:
846 # override functions with win32 versions if possible
842 # override functions with win32 versions if possible
847 from util_win32 import *
843 from util_win32 import *
848 if not is_win_9x():
844 if not is_win_9x():
849 posixfile = posixfile_nt
845 posixfile = posixfile_nt
850 except ImportError:
846 except ImportError:
851 pass
847 pass
852
848
853 else:
849 else:
854 nulldev = '/dev/null'
850 nulldev = '/dev/null'
855
851
856 def rcfiles(path):
852 def rcfiles(path):
857 rcs = [os.path.join(path, 'hgrc')]
853 rcs = [os.path.join(path, 'hgrc')]
858 rcdir = os.path.join(path, 'hgrc.d')
854 rcdir = os.path.join(path, 'hgrc.d')
859 try:
855 try:
860 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
856 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
861 if f.endswith(".rc")])
857 if f.endswith(".rc")])
862 except OSError:
858 except OSError:
863 pass
859 pass
864 return rcs
860 return rcs
865
861
866 def os_rcpath():
862 def os_rcpath():
867 '''return default os-specific hgrc search path'''
863 '''return default os-specific hgrc search path'''
868 path = []
864 path = []
869 # old mod_python does not set sys.argv
865 # old mod_python does not set sys.argv
870 if len(getattr(sys, 'argv', [])) > 0:
866 if len(getattr(sys, 'argv', [])) > 0:
871 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
867 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
872 '/../etc/mercurial'))
868 '/../etc/mercurial'))
873 path.extend(rcfiles('/etc/mercurial'))
869 path.extend(rcfiles('/etc/mercurial'))
874 path.append(os.path.expanduser('~/.hgrc'))
870 path.append(os.path.expanduser('~/.hgrc'))
875 path = [os.path.normpath(f) for f in path]
871 path = [os.path.normpath(f) for f in path]
876 return path
872 return path
877
873
878 def parse_patch_output(output_line):
874 def parse_patch_output(output_line):
879 """parses the output produced by patch and returns the file name"""
875 """parses the output produced by patch and returns the file name"""
880 pf = output_line[14:]
876 pf = output_line[14:]
881 if pf.startswith("'") and pf.endswith("'") and " " in pf:
877 if pf.startswith("'") and pf.endswith("'") and " " in pf:
882 pf = pf[1:-1] # Remove the quotes
878 pf = pf[1:-1] # Remove the quotes
883 return pf
879 return pf
884
880
885 def is_exec(f, last):
881 def is_exec(f, last):
886 """check whether a file is executable"""
882 """check whether a file is executable"""
887 return (os.lstat(f).st_mode & 0100 != 0)
883 return (os.lstat(f).st_mode & 0100 != 0)
888
884
889 def set_exec(f, mode):
885 def set_exec(f, mode):
890 s = os.lstat(f).st_mode
886 s = os.lstat(f).st_mode
891 if (s & 0100 != 0) == mode:
887 if (s & 0100 != 0) == mode:
892 return
888 return
893 if mode:
889 if mode:
894 # Turn on +x for every +r bit when making a file executable
890 # Turn on +x for every +r bit when making a file executable
895 # and obey umask.
891 # and obey umask.
896 umask = os.umask(0)
892 umask = os.umask(0)
897 os.umask(umask)
893 os.umask(umask)
898 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
894 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
899 else:
895 else:
900 os.chmod(f, s & 0666)
896 os.chmod(f, s & 0666)
901
897
902 def set_binary(fd):
898 def set_binary(fd):
903 pass
899 pass
904
900
905 def pconvert(path):
901 def pconvert(path):
906 return path
902 return path
907
903
908 def localpath(path):
904 def localpath(path):
909 return path
905 return path
910
906
911 normpath = os.path.normpath
907 normpath = os.path.normpath
912 samestat = os.path.samestat
908 samestat = os.path.samestat
913
909
914 def makelock(info, pathname):
910 def makelock(info, pathname):
915 try:
911 try:
916 os.symlink(info, pathname)
912 os.symlink(info, pathname)
917 except OSError, why:
913 except OSError, why:
918 if why.errno == errno.EEXIST:
914 if why.errno == errno.EEXIST:
919 raise
915 raise
920 else:
916 else:
921 _makelock_file(info, pathname)
917 _makelock_file(info, pathname)
922
918
923 def readlock(pathname):
919 def readlock(pathname):
924 try:
920 try:
925 return os.readlink(pathname)
921 return os.readlink(pathname)
926 except OSError, why:
922 except OSError, why:
927 if why.errno == errno.EINVAL:
923 if why.errno == errno.EINVAL:
928 return _readlock_file(pathname)
924 return _readlock_file(pathname)
929 else:
925 else:
930 raise
926 raise
931
927
932 def shellquote(s):
928 def shellquote(s):
933 return "'%s'" % s.replace("'", "'\\''")
929 return "'%s'" % s.replace("'", "'\\''")
934
930
935 def testpid(pid):
931 def testpid(pid):
936 '''return False if pid dead, True if running or not sure'''
932 '''return False if pid dead, True if running or not sure'''
937 try:
933 try:
938 os.kill(pid, 0)
934 os.kill(pid, 0)
939 return True
935 return True
940 except OSError, inst:
936 except OSError, inst:
941 return inst.errno != errno.ESRCH
937 return inst.errno != errno.ESRCH
942
938
943 def explain_exit(code):
939 def explain_exit(code):
944 """return a 2-tuple (desc, code) describing a process's status"""
940 """return a 2-tuple (desc, code) describing a process's status"""
945 if os.WIFEXITED(code):
941 if os.WIFEXITED(code):
946 val = os.WEXITSTATUS(code)
942 val = os.WEXITSTATUS(code)
947 return _("exited with status %d") % val, val
943 return _("exited with status %d") % val, val
948 elif os.WIFSIGNALED(code):
944 elif os.WIFSIGNALED(code):
949 val = os.WTERMSIG(code)
945 val = os.WTERMSIG(code)
950 return _("killed by signal %d") % val, val
946 return _("killed by signal %d") % val, val
951 elif os.WIFSTOPPED(code):
947 elif os.WIFSTOPPED(code):
952 val = os.WSTOPSIG(code)
948 val = os.WSTOPSIG(code)
953 return _("stopped by signal %d") % val, val
949 return _("stopped by signal %d") % val, val
954 raise ValueError(_("invalid exit code"))
950 raise ValueError(_("invalid exit code"))
955
951
956 def isowner(fp, st=None):
952 def isowner(fp, st=None):
957 """Return True if the file object f belongs to the current user.
953 """Return True if the file object f belongs to the current user.
958
954
959 The return value of a util.fstat(f) may be passed as the st argument.
955 The return value of a util.fstat(f) may be passed as the st argument.
960 """
956 """
961 if st is None:
957 if st is None:
962 st = fstat(fp)
958 st = fstat(fp)
963 return st.st_uid == os.getuid()
959 return st.st_uid == os.getuid()
964
960
965 def _buildencodefun():
961 def _buildencodefun():
966 e = '_'
962 e = '_'
967 win_reserved = [ord(x) for x in '\\:*?"<>|']
963 win_reserved = [ord(x) for x in '\\:*?"<>|']
968 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
964 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
969 for x in (range(32) + range(126, 256) + win_reserved):
965 for x in (range(32) + range(126, 256) + win_reserved):
970 cmap[chr(x)] = "~%02x" % x
966 cmap[chr(x)] = "~%02x" % x
971 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
967 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
972 cmap[chr(x)] = e + chr(x).lower()
968 cmap[chr(x)] = e + chr(x).lower()
973 dmap = {}
969 dmap = {}
974 for k, v in cmap.iteritems():
970 for k, v in cmap.iteritems():
975 dmap[v] = k
971 dmap[v] = k
976 def decode(s):
972 def decode(s):
977 i = 0
973 i = 0
978 while i < len(s):
974 while i < len(s):
979 for l in xrange(1, 4):
975 for l in xrange(1, 4):
980 try:
976 try:
981 yield dmap[s[i:i+l]]
977 yield dmap[s[i:i+l]]
982 i += l
978 i += l
983 break
979 break
984 except KeyError:
980 except KeyError:
985 pass
981 pass
986 else:
982 else:
987 raise KeyError
983 raise KeyError
988 return (lambda s: "".join([cmap[c] for c in s]),
984 return (lambda s: "".join([cmap[c] for c in s]),
989 lambda s: "".join(list(decode(s))))
985 lambda s: "".join(list(decode(s))))
990
986
991 encodefilename, decodefilename = _buildencodefun()
987 encodefilename, decodefilename = _buildencodefun()
992
988
993 def encodedopener(openerfn, fn):
989 def encodedopener(openerfn, fn):
994 def o(path, *args, **kw):
990 def o(path, *args, **kw):
995 return openerfn(fn(path), *args, **kw)
991 return openerfn(fn(path), *args, **kw)
996 return o
992 return o
997
993
998 def opener(base, audit=True):
994 def opener(base, audit=True):
999 """
995 """
1000 return a function that opens files relative to base
996 return a function that opens files relative to base
1001
997
1002 this function is used to hide the details of COW semantics and
998 this function is used to hide the details of COW semantics and
1003 remote file access from higher level code.
999 remote file access from higher level code.
1004 """
1000 """
1005 p = base
1001 p = base
1006 audit_p = audit
1002 audit_p = audit
1007
1003
1008 def mktempcopy(name):
1004 def mktempcopy(name):
1009 d, fn = os.path.split(name)
1005 d, fn = os.path.split(name)
1010 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1006 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1011 os.close(fd)
1007 os.close(fd)
1012 ofp = posixfile(temp, "wb")
1008 ofp = posixfile(temp, "wb")
1013 try:
1009 try:
1014 try:
1010 try:
1015 ifp = posixfile(name, "rb")
1011 ifp = posixfile(name, "rb")
1016 except IOError, inst:
1012 except IOError, inst:
1017 if not getattr(inst, 'filename', None):
1013 if not getattr(inst, 'filename', None):
1018 inst.filename = name
1014 inst.filename = name
1019 raise
1015 raise
1020 for chunk in filechunkiter(ifp):
1016 for chunk in filechunkiter(ifp):
1021 ofp.write(chunk)
1017 ofp.write(chunk)
1022 ifp.close()
1018 ifp.close()
1023 ofp.close()
1019 ofp.close()
1024 except:
1020 except:
1025 try: os.unlink(temp)
1021 try: os.unlink(temp)
1026 except: pass
1022 except: pass
1027 raise
1023 raise
1028 st = os.lstat(name)
1024 st = os.lstat(name)
1029 os.chmod(temp, st.st_mode)
1025 os.chmod(temp, st.st_mode)
1030 return temp
1026 return temp
1031
1027
1032 class atomictempfile(posixfile):
1028 class atomictempfile(posixfile):
1033 """the file will only be copied when rename is called"""
1029 """the file will only be copied when rename is called"""
1034 def __init__(self, name, mode):
1030 def __init__(self, name, mode):
1035 self.__name = name
1031 self.__name = name
1036 self.temp = mktempcopy(name)
1032 self.temp = mktempcopy(name)
1037 posixfile.__init__(self, self.temp, mode)
1033 posixfile.__init__(self, self.temp, mode)
1038 def rename(self):
1034 def rename(self):
1039 if not self.closed:
1035 if not self.closed:
1040 posixfile.close(self)
1036 posixfile.close(self)
1041 rename(self.temp, localpath(self.__name))
1037 rename(self.temp, localpath(self.__name))
1042 def __del__(self):
1038 def __del__(self):
1043 if not self.closed:
1039 if not self.closed:
1044 try:
1040 try:
1045 os.unlink(self.temp)
1041 os.unlink(self.temp)
1046 except: pass
1042 except: pass
1047 posixfile.close(self)
1043 posixfile.close(self)
1048
1044
1049 class atomicfile(atomictempfile):
1045 class atomicfile(atomictempfile):
1050 """the file will only be copied on close"""
1046 """the file will only be copied on close"""
1051 def __init__(self, name, mode):
1047 def __init__(self, name, mode):
1052 atomictempfile.__init__(self, name, mode)
1048 atomictempfile.__init__(self, name, mode)
1053 def close(self):
1049 def close(self):
1054 self.rename()
1050 self.rename()
1055 def __del__(self):
1051 def __del__(self):
1056 self.rename()
1052 self.rename()
1057
1053
1058 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1054 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1059 if audit_p:
1055 if audit_p:
1060 audit_path(path)
1056 audit_path(path)
1061 f = os.path.join(p, path)
1057 f = os.path.join(p, path)
1062
1058
1063 if not text:
1059 if not text:
1064 mode += "b" # for that other OS
1060 mode += "b" # for that other OS
1065
1061
1066 if mode[0] != "r":
1062 if mode[0] != "r":
1067 try:
1063 try:
1068 nlink = nlinks(f)
1064 nlink = nlinks(f)
1069 except OSError:
1065 except OSError:
1070 d = os.path.dirname(f)
1066 d = os.path.dirname(f)
1071 if not os.path.isdir(d):
1067 if not os.path.isdir(d):
1072 os.makedirs(d)
1068 os.makedirs(d)
1073 else:
1069 else:
1074 if atomic:
1070 if atomic:
1075 return atomicfile(f, mode)
1071 return atomicfile(f, mode)
1076 elif atomictemp:
1072 elif atomictemp:
1077 return atomictempfile(f, mode)
1073 return atomictempfile(f, mode)
1078 if nlink > 1:
1074 if nlink > 1:
1079 rename(mktempcopy(f), f)
1075 rename(mktempcopy(f), f)
1080 return posixfile(f, mode)
1076 return posixfile(f, mode)
1081
1077
1082 return o
1078 return o
1083
1079
1084 class chunkbuffer(object):
1080 class chunkbuffer(object):
1085 """Allow arbitrary sized chunks of data to be efficiently read from an
1081 """Allow arbitrary sized chunks of data to be efficiently read from an
1086 iterator over chunks of arbitrary size."""
1082 iterator over chunks of arbitrary size."""
1087
1083
1088 def __init__(self, in_iter, targetsize = 2**16):
1084 def __init__(self, in_iter, targetsize = 2**16):
1089 """in_iter is the iterator that's iterating over the input chunks.
1085 """in_iter is the iterator that's iterating over the input chunks.
1090 targetsize is how big a buffer to try to maintain."""
1086 targetsize is how big a buffer to try to maintain."""
1091 self.in_iter = iter(in_iter)
1087 self.in_iter = iter(in_iter)
1092 self.buf = ''
1088 self.buf = ''
1093 self.targetsize = int(targetsize)
1089 self.targetsize = int(targetsize)
1094 if self.targetsize <= 0:
1090 if self.targetsize <= 0:
1095 raise ValueError(_("targetsize must be greater than 0, was %d") %
1091 raise ValueError(_("targetsize must be greater than 0, was %d") %
1096 targetsize)
1092 targetsize)
1097 self.iterempty = False
1093 self.iterempty = False
1098
1094
1099 def fillbuf(self):
1095 def fillbuf(self):
1100 """Ignore target size; read every chunk from iterator until empty."""
1096 """Ignore target size; read every chunk from iterator until empty."""
1101 if not self.iterempty:
1097 if not self.iterempty:
1102 collector = cStringIO.StringIO()
1098 collector = cStringIO.StringIO()
1103 collector.write(self.buf)
1099 collector.write(self.buf)
1104 for ch in self.in_iter:
1100 for ch in self.in_iter:
1105 collector.write(ch)
1101 collector.write(ch)
1106 self.buf = collector.getvalue()
1102 self.buf = collector.getvalue()
1107 self.iterempty = True
1103 self.iterempty = True
1108
1104
1109 def read(self, l):
1105 def read(self, l):
1110 """Read L bytes of data from the iterator of chunks of data.
1106 """Read L bytes of data from the iterator of chunks of data.
1111 Returns less than L bytes if the iterator runs dry."""
1107 Returns less than L bytes if the iterator runs dry."""
1112 if l > len(self.buf) and not self.iterempty:
1108 if l > len(self.buf) and not self.iterempty:
1113 # Clamp to a multiple of self.targetsize
1109 # Clamp to a multiple of self.targetsize
1114 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1110 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1115 collector = cStringIO.StringIO()
1111 collector = cStringIO.StringIO()
1116 collector.write(self.buf)
1112 collector.write(self.buf)
1117 collected = len(self.buf)
1113 collected = len(self.buf)
1118 for chunk in self.in_iter:
1114 for chunk in self.in_iter:
1119 collector.write(chunk)
1115 collector.write(chunk)
1120 collected += len(chunk)
1116 collected += len(chunk)
1121 if collected >= targetsize:
1117 if collected >= targetsize:
1122 break
1118 break
1123 if collected < targetsize:
1119 if collected < targetsize:
1124 self.iterempty = True
1120 self.iterempty = True
1125 self.buf = collector.getvalue()
1121 self.buf = collector.getvalue()
1126 s, self.buf = self.buf[:l], buffer(self.buf, l)
1122 s, self.buf = self.buf[:l], buffer(self.buf, l)
1127 return s
1123 return s
1128
1124
1129 def filechunkiter(f, size=65536, limit=None):
1125 def filechunkiter(f, size=65536, limit=None):
1130 """Create a generator that produces the data in the file size
1126 """Create a generator that produces the data in the file size
1131 (default 65536) bytes at a time, up to optional limit (default is
1127 (default 65536) bytes at a time, up to optional limit (default is
1132 to read all data). Chunks may be less than size bytes if the
1128 to read all data). Chunks may be less than size bytes if the
1133 chunk is the last chunk in the file, or the file is a socket or
1129 chunk is the last chunk in the file, or the file is a socket or
1134 some other type of file that sometimes reads less data than is
1130 some other type of file that sometimes reads less data than is
1135 requested."""
1131 requested."""
1136 assert size >= 0
1132 assert size >= 0
1137 assert limit is None or limit >= 0
1133 assert limit is None or limit >= 0
1138 while True:
1134 while True:
1139 if limit is None: nbytes = size
1135 if limit is None: nbytes = size
1140 else: nbytes = min(limit, size)
1136 else: nbytes = min(limit, size)
1141 s = nbytes and f.read(nbytes)
1137 s = nbytes and f.read(nbytes)
1142 if not s: break
1138 if not s: break
1143 if limit: limit -= len(s)
1139 if limit: limit -= len(s)
1144 yield s
1140 yield s
1145
1141
1146 def makedate():
1142 def makedate():
1147 lt = time.localtime()
1143 lt = time.localtime()
1148 if lt[8] == 1 and time.daylight:
1144 if lt[8] == 1 and time.daylight:
1149 tz = time.altzone
1145 tz = time.altzone
1150 else:
1146 else:
1151 tz = time.timezone
1147 tz = time.timezone
1152 return time.mktime(lt), tz
1148 return time.mktime(lt), tz
1153
1149
1154 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1150 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1155 """represent a (unixtime, offset) tuple as a localized time.
1151 """represent a (unixtime, offset) tuple as a localized time.
1156 unixtime is seconds since the epoch, and offset is the time zone's
1152 unixtime is seconds since the epoch, and offset is the time zone's
1157 number of seconds away from UTC. if timezone is false, do not
1153 number of seconds away from UTC. if timezone is false, do not
1158 append time zone to string."""
1154 append time zone to string."""
1159 t, tz = date or makedate()
1155 t, tz = date or makedate()
1160 s = time.strftime(format, time.gmtime(float(t) - tz))
1156 s = time.strftime(format, time.gmtime(float(t) - tz))
1161 if timezone:
1157 if timezone:
1162 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1158 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1163 return s
1159 return s
1164
1160
1165 def strdate(string, format, defaults):
1161 def strdate(string, format, defaults):
1166 """parse a localized time string and return a (unixtime, offset) tuple.
1162 """parse a localized time string and return a (unixtime, offset) tuple.
1167 if the string cannot be parsed, ValueError is raised."""
1163 if the string cannot be parsed, ValueError is raised."""
1168 def timezone(string):
1164 def timezone(string):
1169 tz = string.split()[-1]
1165 tz = string.split()[-1]
1170 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1166 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1171 tz = int(tz)
1167 tz = int(tz)
1172 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1168 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1173 return offset
1169 return offset
1174 if tz == "GMT" or tz == "UTC":
1170 if tz == "GMT" or tz == "UTC":
1175 return 0
1171 return 0
1176 return None
1172 return None
1177
1173
1178 # NOTE: unixtime = localunixtime + offset
1174 # NOTE: unixtime = localunixtime + offset
1179 offset, date = timezone(string), string
1175 offset, date = timezone(string), string
1180 if offset != None:
1176 if offset != None:
1181 date = " ".join(string.split()[:-1])
1177 date = " ".join(string.split()[:-1])
1182
1178
1183 # add missing elements from defaults
1179 # add missing elements from defaults
1184 for part in defaults:
1180 for part in defaults:
1185 found = [True for p in part if ("%"+p) in format]
1181 found = [True for p in part if ("%"+p) in format]
1186 if not found:
1182 if not found:
1187 date += "@" + defaults[part]
1183 date += "@" + defaults[part]
1188 format += "@%" + part[0]
1184 format += "@%" + part[0]
1189
1185
1190 timetuple = time.strptime(date, format)
1186 timetuple = time.strptime(date, format)
1191 localunixtime = int(calendar.timegm(timetuple))
1187 localunixtime = int(calendar.timegm(timetuple))
1192 if offset is None:
1188 if offset is None:
1193 # local timezone
1189 # local timezone
1194 unixtime = int(time.mktime(timetuple))
1190 unixtime = int(time.mktime(timetuple))
1195 offset = unixtime - localunixtime
1191 offset = unixtime - localunixtime
1196 else:
1192 else:
1197 unixtime = localunixtime + offset
1193 unixtime = localunixtime + offset
1198 return unixtime, offset
1194 return unixtime, offset
1199
1195
1200 def parsedate(string, formats=None, defaults=None):
1196 def parsedate(string, formats=None, defaults=None):
1201 """parse a localized time string and return a (unixtime, offset) tuple.
1197 """parse a localized time string and return a (unixtime, offset) tuple.
1202 The date may be a "unixtime offset" string or in one of the specified
1198 The date may be a "unixtime offset" string or in one of the specified
1203 formats."""
1199 formats."""
1204 if not string:
1200 if not string:
1205 return 0, 0
1201 return 0, 0
1206 if not formats:
1202 if not formats:
1207 formats = defaultdateformats
1203 formats = defaultdateformats
1208 string = string.strip()
1204 string = string.strip()
1209 try:
1205 try:
1210 when, offset = map(int, string.split(' '))
1206 when, offset = map(int, string.split(' '))
1211 except ValueError:
1207 except ValueError:
1212 # fill out defaults
1208 # fill out defaults
1213 if not defaults:
1209 if not defaults:
1214 defaults = {}
1210 defaults = {}
1215 now = makedate()
1211 now = makedate()
1216 for part in "d mb yY HI M S".split():
1212 for part in "d mb yY HI M S".split():
1217 if part not in defaults:
1213 if part not in defaults:
1218 if part[0] in "HMS":
1214 if part[0] in "HMS":
1219 defaults[part] = "00"
1215 defaults[part] = "00"
1220 elif part[0] in "dm":
1216 elif part[0] in "dm":
1221 defaults[part] = "1"
1217 defaults[part] = "1"
1222 else:
1218 else:
1223 defaults[part] = datestr(now, "%" + part[0], False)
1219 defaults[part] = datestr(now, "%" + part[0], False)
1224
1220
1225 for format in formats:
1221 for format in formats:
1226 try:
1222 try:
1227 when, offset = strdate(string, format, defaults)
1223 when, offset = strdate(string, format, defaults)
1228 except ValueError:
1224 except ValueError:
1229 pass
1225 pass
1230 else:
1226 else:
1231 break
1227 break
1232 else:
1228 else:
1233 raise Abort(_('invalid date: %r ') % string)
1229 raise Abort(_('invalid date: %r ') % string)
1234 # validate explicit (probably user-specified) date and
1230 # validate explicit (probably user-specified) date and
1235 # time zone offset. values must fit in signed 32 bits for
1231 # time zone offset. values must fit in signed 32 bits for
1236 # current 32-bit linux runtimes. timezones go from UTC-12
1232 # current 32-bit linux runtimes. timezones go from UTC-12
1237 # to UTC+14
1233 # to UTC+14
1238 if abs(when) > 0x7fffffff:
1234 if abs(when) > 0x7fffffff:
1239 raise Abort(_('date exceeds 32 bits: %d') % when)
1235 raise Abort(_('date exceeds 32 bits: %d') % when)
1240 if offset < -50400 or offset > 43200:
1236 if offset < -50400 or offset > 43200:
1241 raise Abort(_('impossible time zone offset: %d') % offset)
1237 raise Abort(_('impossible time zone offset: %d') % offset)
1242 return when, offset
1238 return when, offset
1243
1239
1244 def matchdate(date):
1240 def matchdate(date):
1245 """Return a function that matches a given date match specifier
1241 """Return a function that matches a given date match specifier
1246
1242
1247 Formats include:
1243 Formats include:
1248
1244
1249 '{date}' match a given date to the accuracy provided
1245 '{date}' match a given date to the accuracy provided
1250
1246
1251 '<{date}' on or before a given date
1247 '<{date}' on or before a given date
1252
1248
1253 '>{date}' on or after a given date
1249 '>{date}' on or after a given date
1254
1250
1255 """
1251 """
1256
1252
1257 def lower(date):
1253 def lower(date):
1258 return parsedate(date, extendeddateformats)[0]
1254 return parsedate(date, extendeddateformats)[0]
1259
1255
1260 def upper(date):
1256 def upper(date):
1261 d = dict(mb="12", HI="23", M="59", S="59")
1257 d = dict(mb="12", HI="23", M="59", S="59")
1262 for days in "31 30 29".split():
1258 for days in "31 30 29".split():
1263 try:
1259 try:
1264 d["d"] = days
1260 d["d"] = days
1265 return parsedate(date, extendeddateformats, d)[0]
1261 return parsedate(date, extendeddateformats, d)[0]
1266 except:
1262 except:
1267 pass
1263 pass
1268 d["d"] = "28"
1264 d["d"] = "28"
1269 return parsedate(date, extendeddateformats, d)[0]
1265 return parsedate(date, extendeddateformats, d)[0]
1270
1266
1271 if date[0] == "<":
1267 if date[0] == "<":
1272 when = upper(date[1:])
1268 when = upper(date[1:])
1273 return lambda x: x <= when
1269 return lambda x: x <= when
1274 elif date[0] == ">":
1270 elif date[0] == ">":
1275 when = lower(date[1:])
1271 when = lower(date[1:])
1276 return lambda x: x >= when
1272 return lambda x: x >= when
1277 elif date[0] == "-":
1273 elif date[0] == "-":
1278 try:
1274 try:
1279 days = int(date[1:])
1275 days = int(date[1:])
1280 except ValueError:
1276 except ValueError:
1281 raise Abort(_("invalid day spec: %s") % date[1:])
1277 raise Abort(_("invalid day spec: %s") % date[1:])
1282 when = makedate()[0] - days * 3600 * 24
1278 when = makedate()[0] - days * 3600 * 24
1283 return lambda x: x >= when
1279 return lambda x: x >= when
1284 elif " to " in date:
1280 elif " to " in date:
1285 a, b = date.split(" to ")
1281 a, b = date.split(" to ")
1286 start, stop = lower(a), upper(b)
1282 start, stop = lower(a), upper(b)
1287 return lambda x: x >= start and x <= stop
1283 return lambda x: x >= start and x <= stop
1288 else:
1284 else:
1289 start, stop = lower(date), upper(date)
1285 start, stop = lower(date), upper(date)
1290 return lambda x: x >= start and x <= stop
1286 return lambda x: x >= start and x <= stop
1291
1287
1292 def shortuser(user):
1288 def shortuser(user):
1293 """Return a short representation of a user name or email address."""
1289 """Return a short representation of a user name or email address."""
1294 f = user.find('@')
1290 f = user.find('@')
1295 if f >= 0:
1291 if f >= 0:
1296 user = user[:f]
1292 user = user[:f]
1297 f = user.find('<')
1293 f = user.find('<')
1298 if f >= 0:
1294 if f >= 0:
1299 user = user[f+1:]
1295 user = user[f+1:]
1300 f = user.find(' ')
1296 f = user.find(' ')
1301 if f >= 0:
1297 if f >= 0:
1302 user = user[:f]
1298 user = user[:f]
1303 f = user.find('.')
1299 f = user.find('.')
1304 if f >= 0:
1300 if f >= 0:
1305 user = user[:f]
1301 user = user[:f]
1306 return user
1302 return user
1307
1303
1308 def ellipsis(text, maxlength=400):
1304 def ellipsis(text, maxlength=400):
1309 """Trim string to at most maxlength (default: 400) characters."""
1305 """Trim string to at most maxlength (default: 400) characters."""
1310 if len(text) <= maxlength:
1306 if len(text) <= maxlength:
1311 return text
1307 return text
1312 else:
1308 else:
1313 return "%s..." % (text[:maxlength-3])
1309 return "%s..." % (text[:maxlength-3])
1314
1310
1315 def walkrepos(path):
1311 def walkrepos(path):
1316 '''yield every hg repository under path, recursively.'''
1312 '''yield every hg repository under path, recursively.'''
1317 def errhandler(err):
1313 def errhandler(err):
1318 if err.filename == path:
1314 if err.filename == path:
1319 raise err
1315 raise err
1320
1316
1321 for root, dirs, files in os.walk(path, onerror=errhandler):
1317 for root, dirs, files in os.walk(path, onerror=errhandler):
1322 for d in dirs:
1318 for d in dirs:
1323 if d == '.hg':
1319 if d == '.hg':
1324 yield root
1320 yield root
1325 dirs[:] = []
1321 dirs[:] = []
1326 break
1322 break
1327
1323
1328 _rcpath = None
1324 _rcpath = None
1329
1325
1330 def rcpath():
1326 def rcpath():
1331 '''return hgrc search path. if env var HGRCPATH is set, use it.
1327 '''return hgrc search path. if env var HGRCPATH is set, use it.
1332 for each item in path, if directory, use files ending in .rc,
1328 for each item in path, if directory, use files ending in .rc,
1333 else use item.
1329 else use item.
1334 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1330 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1335 if no HGRCPATH, use default os-specific path.'''
1331 if no HGRCPATH, use default os-specific path.'''
1336 global _rcpath
1332 global _rcpath
1337 if _rcpath is None:
1333 if _rcpath is None:
1338 if 'HGRCPATH' in os.environ:
1334 if 'HGRCPATH' in os.environ:
1339 _rcpath = []
1335 _rcpath = []
1340 for p in os.environ['HGRCPATH'].split(os.pathsep):
1336 for p in os.environ['HGRCPATH'].split(os.pathsep):
1341 if not p: continue
1337 if not p: continue
1342 if os.path.isdir(p):
1338 if os.path.isdir(p):
1343 for f in os.listdir(p):
1339 for f in os.listdir(p):
1344 if f.endswith('.rc'):
1340 if f.endswith('.rc'):
1345 _rcpath.append(os.path.join(p, f))
1341 _rcpath.append(os.path.join(p, f))
1346 else:
1342 else:
1347 _rcpath.append(p)
1343 _rcpath.append(p)
1348 else:
1344 else:
1349 _rcpath = os_rcpath()
1345 _rcpath = os_rcpath()
1350 return _rcpath
1346 return _rcpath
1351
1347
1352 def bytecount(nbytes):
1348 def bytecount(nbytes):
1353 '''return byte count formatted as readable string, with units'''
1349 '''return byte count formatted as readable string, with units'''
1354
1350
1355 units = (
1351 units = (
1356 (100, 1<<30, _('%.0f GB')),
1352 (100, 1<<30, _('%.0f GB')),
1357 (10, 1<<30, _('%.1f GB')),
1353 (10, 1<<30, _('%.1f GB')),
1358 (1, 1<<30, _('%.2f GB')),
1354 (1, 1<<30, _('%.2f GB')),
1359 (100, 1<<20, _('%.0f MB')),
1355 (100, 1<<20, _('%.0f MB')),
1360 (10, 1<<20, _('%.1f MB')),
1356 (10, 1<<20, _('%.1f MB')),
1361 (1, 1<<20, _('%.2f MB')),
1357 (1, 1<<20, _('%.2f MB')),
1362 (100, 1<<10, _('%.0f KB')),
1358 (100, 1<<10, _('%.0f KB')),
1363 (10, 1<<10, _('%.1f KB')),
1359 (10, 1<<10, _('%.1f KB')),
1364 (1, 1<<10, _('%.2f KB')),
1360 (1, 1<<10, _('%.2f KB')),
1365 (1, 1, _('%.0f bytes')),
1361 (1, 1, _('%.0f bytes')),
1366 )
1362 )
1367
1363
1368 for multiplier, divisor, format in units:
1364 for multiplier, divisor, format in units:
1369 if nbytes >= divisor * multiplier:
1365 if nbytes >= divisor * multiplier:
1370 return format % (nbytes / float(divisor))
1366 return format % (nbytes / float(divisor))
1371 return units[-1][2] % nbytes
1367 return units[-1][2] % nbytes
1372
1368
1373 def drop_scheme(scheme, path):
1369 def drop_scheme(scheme, path):
1374 sc = scheme + ':'
1370 sc = scheme + ':'
1375 if path.startswith(sc):
1371 if path.startswith(sc):
1376 path = path[len(sc):]
1372 path = path[len(sc):]
1377 if path.startswith('//'):
1373 if path.startswith('//'):
1378 path = path[2:]
1374 path = path[2:]
1379 return path
1375 return path
General Comments 0
You need to be logged in to leave comments. Login now