##// END OF EJS Templates
hgweb: show correct error message for i18n environment...
Takumi IINO -
r18855:50c922c1 2.5.3 stable
parent child Browse files
Show More
@@ -1,1364 +1,1364 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import copies
11 import copies
12 import match as matchmod
12 import match as matchmod
13 import os, errno, stat
13 import os, errno, stat
14 import obsolete as obsmod
14 import obsolete as obsmod
15 import repoview
15 import repoview
16
16
17 propertycache = util.propertycache
17 propertycache = util.propertycache
18
18
19 class changectx(object):
19 class changectx(object):
20 """A changecontext object makes access to data related to a particular
20 """A changecontext object makes access to data related to a particular
21 changeset convenient."""
21 changeset convenient."""
22 def __init__(self, repo, changeid=''):
22 def __init__(self, repo, changeid=''):
23 """changeid is a revision number, node, or tag"""
23 """changeid is a revision number, node, or tag"""
24 if changeid == '':
24 if changeid == '':
25 changeid = '.'
25 changeid = '.'
26 self._repo = repo
26 self._repo = repo
27
27
28 if isinstance(changeid, int):
28 if isinstance(changeid, int):
29 try:
29 try:
30 self._node = repo.changelog.node(changeid)
30 self._node = repo.changelog.node(changeid)
31 except IndexError:
31 except IndexError:
32 raise error.RepoLookupError(
32 raise error.RepoLookupError(
33 _("unknown revision '%s'") % changeid)
33 _("unknown revision '%s'") % changeid)
34 self._rev = changeid
34 self._rev = changeid
35 return
35 return
36 if isinstance(changeid, long):
36 if isinstance(changeid, long):
37 changeid = str(changeid)
37 changeid = str(changeid)
38 if changeid == '.':
38 if changeid == '.':
39 self._node = repo.dirstate.p1()
39 self._node = repo.dirstate.p1()
40 self._rev = repo.changelog.rev(self._node)
40 self._rev = repo.changelog.rev(self._node)
41 return
41 return
42 if changeid == 'null':
42 if changeid == 'null':
43 self._node = nullid
43 self._node = nullid
44 self._rev = nullrev
44 self._rev = nullrev
45 return
45 return
46 if changeid == 'tip':
46 if changeid == 'tip':
47 self._node = repo.changelog.tip()
47 self._node = repo.changelog.tip()
48 self._rev = repo.changelog.rev(self._node)
48 self._rev = repo.changelog.rev(self._node)
49 return
49 return
50 if len(changeid) == 20:
50 if len(changeid) == 20:
51 try:
51 try:
52 self._node = changeid
52 self._node = changeid
53 self._rev = repo.changelog.rev(changeid)
53 self._rev = repo.changelog.rev(changeid)
54 return
54 return
55 except LookupError:
55 except LookupError:
56 pass
56 pass
57
57
58 try:
58 try:
59 r = int(changeid)
59 r = int(changeid)
60 if str(r) != changeid:
60 if str(r) != changeid:
61 raise ValueError
61 raise ValueError
62 l = len(repo.changelog)
62 l = len(repo.changelog)
63 if r < 0:
63 if r < 0:
64 r += l
64 r += l
65 if r < 0 or r >= l:
65 if r < 0 or r >= l:
66 raise ValueError
66 raise ValueError
67 self._rev = r
67 self._rev = r
68 self._node = repo.changelog.node(r)
68 self._node = repo.changelog.node(r)
69 return
69 return
70 except (ValueError, OverflowError, IndexError):
70 except (ValueError, OverflowError, IndexError):
71 pass
71 pass
72
72
73 if len(changeid) == 40:
73 if len(changeid) == 40:
74 try:
74 try:
75 self._node = bin(changeid)
75 self._node = bin(changeid)
76 self._rev = repo.changelog.rev(self._node)
76 self._rev = repo.changelog.rev(self._node)
77 return
77 return
78 except (TypeError, LookupError):
78 except (TypeError, LookupError):
79 pass
79 pass
80
80
81 if changeid in repo._bookmarks:
81 if changeid in repo._bookmarks:
82 self._node = repo._bookmarks[changeid]
82 self._node = repo._bookmarks[changeid]
83 self._rev = repo.changelog.rev(self._node)
83 self._rev = repo.changelog.rev(self._node)
84 return
84 return
85 if changeid in repo._tagscache.tags:
85 if changeid in repo._tagscache.tags:
86 self._node = repo._tagscache.tags[changeid]
86 self._node = repo._tagscache.tags[changeid]
87 self._rev = repo.changelog.rev(self._node)
87 self._rev = repo.changelog.rev(self._node)
88 return
88 return
89 try:
89 try:
90 self._node = repo.branchtip(changeid)
90 self._node = repo.branchtip(changeid)
91 self._rev = repo.changelog.rev(self._node)
91 self._rev = repo.changelog.rev(self._node)
92 return
92 return
93 except error.RepoLookupError:
93 except error.RepoLookupError:
94 pass
94 pass
95
95
96 self._node = repo.changelog._partialmatch(changeid)
96 self._node = repo.changelog._partialmatch(changeid)
97 if self._node is not None:
97 if self._node is not None:
98 self._rev = repo.changelog.rev(self._node)
98 self._rev = repo.changelog.rev(self._node)
99 return
99 return
100
100
101 # lookup failed
101 # lookup failed
102 # check if it might have come from damaged dirstate
102 # check if it might have come from damaged dirstate
103 #
103 #
104 # XXX we could avoid the unfiltered if we had a recognizable exception
104 # XXX we could avoid the unfiltered if we had a recognizable exception
105 # for filtered changeset access
105 # for filtered changeset access
106 if changeid in repo.unfiltered().dirstate.parents():
106 if changeid in repo.unfiltered().dirstate.parents():
107 raise error.Abort(_("working directory has unknown parent '%s'!")
107 raise error.Abort(_("working directory has unknown parent '%s'!")
108 % short(changeid))
108 % short(changeid))
109 try:
109 try:
110 if len(changeid) == 20:
110 if len(changeid) == 20:
111 changeid = hex(changeid)
111 changeid = hex(changeid)
112 except TypeError:
112 except TypeError:
113 pass
113 pass
114 raise error.RepoLookupError(
114 raise error.RepoLookupError(
115 _("unknown revision '%s'") % changeid)
115 _("unknown revision '%s'") % changeid)
116
116
117 def __str__(self):
117 def __str__(self):
118 return short(self.node())
118 return short(self.node())
119
119
120 def __int__(self):
120 def __int__(self):
121 return self.rev()
121 return self.rev()
122
122
123 def __repr__(self):
123 def __repr__(self):
124 return "<changectx %s>" % str(self)
124 return "<changectx %s>" % str(self)
125
125
126 def __hash__(self):
126 def __hash__(self):
127 try:
127 try:
128 return hash(self._rev)
128 return hash(self._rev)
129 except AttributeError:
129 except AttributeError:
130 return id(self)
130 return id(self)
131
131
132 def __eq__(self, other):
132 def __eq__(self, other):
133 try:
133 try:
134 return self._rev == other._rev
134 return self._rev == other._rev
135 except AttributeError:
135 except AttributeError:
136 return False
136 return False
137
137
138 def __ne__(self, other):
138 def __ne__(self, other):
139 return not (self == other)
139 return not (self == other)
140
140
141 def __nonzero__(self):
141 def __nonzero__(self):
142 return self._rev != nullrev
142 return self._rev != nullrev
143
143
144 @propertycache
144 @propertycache
145 def _changeset(self):
145 def _changeset(self):
146 return self._repo.changelog.read(self.rev())
146 return self._repo.changelog.read(self.rev())
147
147
148 @propertycache
148 @propertycache
149 def _manifest(self):
149 def _manifest(self):
150 return self._repo.manifest.read(self._changeset[0])
150 return self._repo.manifest.read(self._changeset[0])
151
151
152 @propertycache
152 @propertycache
153 def _manifestdelta(self):
153 def _manifestdelta(self):
154 return self._repo.manifest.readdelta(self._changeset[0])
154 return self._repo.manifest.readdelta(self._changeset[0])
155
155
156 @propertycache
156 @propertycache
157 def _parents(self):
157 def _parents(self):
158 p = self._repo.changelog.parentrevs(self._rev)
158 p = self._repo.changelog.parentrevs(self._rev)
159 if p[1] == nullrev:
159 if p[1] == nullrev:
160 p = p[:-1]
160 p = p[:-1]
161 return [changectx(self._repo, x) for x in p]
161 return [changectx(self._repo, x) for x in p]
162
162
163 @propertycache
163 @propertycache
164 def substate(self):
164 def substate(self):
165 return subrepo.state(self, self._repo.ui)
165 return subrepo.state(self, self._repo.ui)
166
166
167 def __contains__(self, key):
167 def __contains__(self, key):
168 return key in self._manifest
168 return key in self._manifest
169
169
170 def __getitem__(self, key):
170 def __getitem__(self, key):
171 return self.filectx(key)
171 return self.filectx(key)
172
172
173 def __iter__(self):
173 def __iter__(self):
174 for f in sorted(self._manifest):
174 for f in sorted(self._manifest):
175 yield f
175 yield f
176
176
177 def changeset(self):
177 def changeset(self):
178 return self._changeset
178 return self._changeset
179 def manifest(self):
179 def manifest(self):
180 return self._manifest
180 return self._manifest
181 def manifestnode(self):
181 def manifestnode(self):
182 return self._changeset[0]
182 return self._changeset[0]
183
183
184 def rev(self):
184 def rev(self):
185 return self._rev
185 return self._rev
186 def node(self):
186 def node(self):
187 return self._node
187 return self._node
188 def hex(self):
188 def hex(self):
189 return hex(self._node)
189 return hex(self._node)
190 def user(self):
190 def user(self):
191 return self._changeset[1]
191 return self._changeset[1]
192 def date(self):
192 def date(self):
193 return self._changeset[2]
193 return self._changeset[2]
194 def files(self):
194 def files(self):
195 return self._changeset[3]
195 return self._changeset[3]
196 def description(self):
196 def description(self):
197 return self._changeset[4]
197 return self._changeset[4]
198 def branch(self):
198 def branch(self):
199 return encoding.tolocal(self._changeset[5].get("branch"))
199 return encoding.tolocal(self._changeset[5].get("branch"))
200 def closesbranch(self):
200 def closesbranch(self):
201 return 'close' in self._changeset[5]
201 return 'close' in self._changeset[5]
202 def extra(self):
202 def extra(self):
203 return self._changeset[5]
203 return self._changeset[5]
204 def tags(self):
204 def tags(self):
205 return self._repo.nodetags(self._node)
205 return self._repo.nodetags(self._node)
206 def bookmarks(self):
206 def bookmarks(self):
207 return self._repo.nodebookmarks(self._node)
207 return self._repo.nodebookmarks(self._node)
208 def phase(self):
208 def phase(self):
209 return self._repo._phasecache.phase(self._repo, self._rev)
209 return self._repo._phasecache.phase(self._repo, self._rev)
210 def phasestr(self):
210 def phasestr(self):
211 return phases.phasenames[self.phase()]
211 return phases.phasenames[self.phase()]
212 def mutable(self):
212 def mutable(self):
213 return self.phase() > phases.public
213 return self.phase() > phases.public
214 def hidden(self):
214 def hidden(self):
215 return self._rev in repoview.filterrevs(self._repo, 'visible')
215 return self._rev in repoview.filterrevs(self._repo, 'visible')
216
216
217 def parents(self):
217 def parents(self):
218 """return contexts for each parent changeset"""
218 """return contexts for each parent changeset"""
219 return self._parents
219 return self._parents
220
220
221 def p1(self):
221 def p1(self):
222 return self._parents[0]
222 return self._parents[0]
223
223
224 def p2(self):
224 def p2(self):
225 if len(self._parents) == 2:
225 if len(self._parents) == 2:
226 return self._parents[1]
226 return self._parents[1]
227 return changectx(self._repo, -1)
227 return changectx(self._repo, -1)
228
228
229 def children(self):
229 def children(self):
230 """return contexts for each child changeset"""
230 """return contexts for each child changeset"""
231 c = self._repo.changelog.children(self._node)
231 c = self._repo.changelog.children(self._node)
232 return [changectx(self._repo, x) for x in c]
232 return [changectx(self._repo, x) for x in c]
233
233
234 def ancestors(self):
234 def ancestors(self):
235 for a in self._repo.changelog.ancestors([self._rev]):
235 for a in self._repo.changelog.ancestors([self._rev]):
236 yield changectx(self._repo, a)
236 yield changectx(self._repo, a)
237
237
238 def descendants(self):
238 def descendants(self):
239 for d in self._repo.changelog.descendants([self._rev]):
239 for d in self._repo.changelog.descendants([self._rev]):
240 yield changectx(self._repo, d)
240 yield changectx(self._repo, d)
241
241
242 def obsolete(self):
242 def obsolete(self):
243 """True if the changeset is obsolete"""
243 """True if the changeset is obsolete"""
244 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
244 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
245
245
246 def extinct(self):
246 def extinct(self):
247 """True if the changeset is extinct"""
247 """True if the changeset is extinct"""
248 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
248 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
249
249
250 def unstable(self):
250 def unstable(self):
251 """True if the changeset is not obsolete but it's ancestor are"""
251 """True if the changeset is not obsolete but it's ancestor are"""
252 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
252 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
253
253
254 def bumped(self):
254 def bumped(self):
255 """True if the changeset try to be a successor of a public changeset
255 """True if the changeset try to be a successor of a public changeset
256
256
257 Only non-public and non-obsolete changesets may be bumped.
257 Only non-public and non-obsolete changesets may be bumped.
258 """
258 """
259 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
259 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
260
260
261 def divergent(self):
261 def divergent(self):
262 """Is a successors of a changeset with multiple possible successors set
262 """Is a successors of a changeset with multiple possible successors set
263
263
264 Only non-public and non-obsolete changesets may be divergent.
264 Only non-public and non-obsolete changesets may be divergent.
265 """
265 """
266 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
266 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
267
267
268 def troubled(self):
268 def troubled(self):
269 """True if the changeset is either unstable, bumped or divergent"""
269 """True if the changeset is either unstable, bumped or divergent"""
270 return self.unstable() or self.bumped() or self.divergent()
270 return self.unstable() or self.bumped() or self.divergent()
271
271
272 def troubles(self):
272 def troubles(self):
273 """return the list of troubles affecting this changesets.
273 """return the list of troubles affecting this changesets.
274
274
275 Troubles are returned as strings. possible values are:
275 Troubles are returned as strings. possible values are:
276 - unstable,
276 - unstable,
277 - bumped,
277 - bumped,
278 - divergent.
278 - divergent.
279 """
279 """
280 troubles = []
280 troubles = []
281 if self.unstable():
281 if self.unstable():
282 troubles.append('unstable')
282 troubles.append('unstable')
283 if self.bumped():
283 if self.bumped():
284 troubles.append('bumped')
284 troubles.append('bumped')
285 if self.divergent():
285 if self.divergent():
286 troubles.append('divergent')
286 troubles.append('divergent')
287 return troubles
287 return troubles
288
288
289 def _fileinfo(self, path):
289 def _fileinfo(self, path):
290 if '_manifest' in self.__dict__:
290 if '_manifest' in self.__dict__:
291 try:
291 try:
292 return self._manifest[path], self._manifest.flags(path)
292 return self._manifest[path], self._manifest.flags(path)
293 except KeyError:
293 except KeyError:
294 raise error.LookupError(self._node, path,
294 raise error.ManifestLookupError(self._node, path,
295 _('not found in manifest'))
295 _('not found in manifest'))
296 if '_manifestdelta' in self.__dict__ or path in self.files():
296 if '_manifestdelta' in self.__dict__ or path in self.files():
297 if path in self._manifestdelta:
297 if path in self._manifestdelta:
298 return (self._manifestdelta[path],
298 return (self._manifestdelta[path],
299 self._manifestdelta.flags(path))
299 self._manifestdelta.flags(path))
300 node, flag = self._repo.manifest.find(self._changeset[0], path)
300 node, flag = self._repo.manifest.find(self._changeset[0], path)
301 if not node:
301 if not node:
302 raise error.LookupError(self._node, path,
302 raise error.ManifestLookupError(self._node, path,
303 _('not found in manifest'))
303 _('not found in manifest'))
304
304
305 return node, flag
305 return node, flag
306
306
307 def filenode(self, path):
307 def filenode(self, path):
308 return self._fileinfo(path)[0]
308 return self._fileinfo(path)[0]
309
309
310 def flags(self, path):
310 def flags(self, path):
311 try:
311 try:
312 return self._fileinfo(path)[1]
312 return self._fileinfo(path)[1]
313 except error.LookupError:
313 except error.LookupError:
314 return ''
314 return ''
315
315
316 def filectx(self, path, fileid=None, filelog=None):
316 def filectx(self, path, fileid=None, filelog=None):
317 """get a file context from this changeset"""
317 """get a file context from this changeset"""
318 if fileid is None:
318 if fileid is None:
319 fileid = self.filenode(path)
319 fileid = self.filenode(path)
320 return filectx(self._repo, path, fileid=fileid,
320 return filectx(self._repo, path, fileid=fileid,
321 changectx=self, filelog=filelog)
321 changectx=self, filelog=filelog)
322
322
323 def ancestor(self, c2):
323 def ancestor(self, c2):
324 """
324 """
325 return the ancestor context of self and c2
325 return the ancestor context of self and c2
326 """
326 """
327 # deal with workingctxs
327 # deal with workingctxs
328 n2 = c2._node
328 n2 = c2._node
329 if n2 is None:
329 if n2 is None:
330 n2 = c2._parents[0]._node
330 n2 = c2._parents[0]._node
331 n = self._repo.changelog.ancestor(self._node, n2)
331 n = self._repo.changelog.ancestor(self._node, n2)
332 return changectx(self._repo, n)
332 return changectx(self._repo, n)
333
333
334 def descendant(self, other):
334 def descendant(self, other):
335 """True if other is descendant of this changeset"""
335 """True if other is descendant of this changeset"""
336 return self._repo.changelog.descendant(self._rev, other._rev)
336 return self._repo.changelog.descendant(self._rev, other._rev)
337
337
338 def walk(self, match):
338 def walk(self, match):
339 fset = set(match.files())
339 fset = set(match.files())
340 # for dirstate.walk, files=['.'] means "walk the whole tree".
340 # for dirstate.walk, files=['.'] means "walk the whole tree".
341 # follow that here, too
341 # follow that here, too
342 fset.discard('.')
342 fset.discard('.')
343 for fn in self:
343 for fn in self:
344 if fn in fset:
344 if fn in fset:
345 # specified pattern is the exact name
345 # specified pattern is the exact name
346 fset.remove(fn)
346 fset.remove(fn)
347 if match(fn):
347 if match(fn):
348 yield fn
348 yield fn
349 for fn in sorted(fset):
349 for fn in sorted(fset):
350 if fn in self._dirs:
350 if fn in self._dirs:
351 # specified pattern is a directory
351 # specified pattern is a directory
352 continue
352 continue
353 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
353 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
354 yield fn
354 yield fn
355
355
356 def sub(self, path):
356 def sub(self, path):
357 return subrepo.subrepo(self, path)
357 return subrepo.subrepo(self, path)
358
358
359 def match(self, pats=[], include=None, exclude=None, default='glob'):
359 def match(self, pats=[], include=None, exclude=None, default='glob'):
360 r = self._repo
360 r = self._repo
361 return matchmod.match(r.root, r.getcwd(), pats,
361 return matchmod.match(r.root, r.getcwd(), pats,
362 include, exclude, default,
362 include, exclude, default,
363 auditor=r.auditor, ctx=self)
363 auditor=r.auditor, ctx=self)
364
364
365 def diff(self, ctx2=None, match=None, **opts):
365 def diff(self, ctx2=None, match=None, **opts):
366 """Returns a diff generator for the given contexts and matcher"""
366 """Returns a diff generator for the given contexts and matcher"""
367 if ctx2 is None:
367 if ctx2 is None:
368 ctx2 = self.p1()
368 ctx2 = self.p1()
369 if ctx2 is not None and not isinstance(ctx2, changectx):
369 if ctx2 is not None and not isinstance(ctx2, changectx):
370 ctx2 = self._repo[ctx2]
370 ctx2 = self._repo[ctx2]
371 diffopts = patch.diffopts(self._repo.ui, opts)
371 diffopts = patch.diffopts(self._repo.ui, opts)
372 return patch.diff(self._repo, ctx2.node(), self.node(),
372 return patch.diff(self._repo, ctx2.node(), self.node(),
373 match=match, opts=diffopts)
373 match=match, opts=diffopts)
374
374
375 @propertycache
375 @propertycache
376 def _dirs(self):
376 def _dirs(self):
377 dirs = set()
377 dirs = set()
378 for f in self._manifest:
378 for f in self._manifest:
379 pos = f.rfind('/')
379 pos = f.rfind('/')
380 while pos != -1:
380 while pos != -1:
381 f = f[:pos]
381 f = f[:pos]
382 if f in dirs:
382 if f in dirs:
383 break # dirs already contains this and above
383 break # dirs already contains this and above
384 dirs.add(f)
384 dirs.add(f)
385 pos = f.rfind('/')
385 pos = f.rfind('/')
386 return dirs
386 return dirs
387
387
388 def dirs(self):
388 def dirs(self):
389 return self._dirs
389 return self._dirs
390
390
391 def dirty(self):
391 def dirty(self):
392 return False
392 return False
393
393
394 class filectx(object):
394 class filectx(object):
395 """A filecontext object makes access to data related to a particular
395 """A filecontext object makes access to data related to a particular
396 filerevision convenient."""
396 filerevision convenient."""
397 def __init__(self, repo, path, changeid=None, fileid=None,
397 def __init__(self, repo, path, changeid=None, fileid=None,
398 filelog=None, changectx=None):
398 filelog=None, changectx=None):
399 """changeid can be a changeset revision, node, or tag.
399 """changeid can be a changeset revision, node, or tag.
400 fileid can be a file revision or node."""
400 fileid can be a file revision or node."""
401 self._repo = repo
401 self._repo = repo
402 self._path = path
402 self._path = path
403
403
404 assert (changeid is not None
404 assert (changeid is not None
405 or fileid is not None
405 or fileid is not None
406 or changectx is not None), \
406 or changectx is not None), \
407 ("bad args: changeid=%r, fileid=%r, changectx=%r"
407 ("bad args: changeid=%r, fileid=%r, changectx=%r"
408 % (changeid, fileid, changectx))
408 % (changeid, fileid, changectx))
409
409
410 if filelog:
410 if filelog:
411 self._filelog = filelog
411 self._filelog = filelog
412
412
413 if changeid is not None:
413 if changeid is not None:
414 self._changeid = changeid
414 self._changeid = changeid
415 if changectx is not None:
415 if changectx is not None:
416 self._changectx = changectx
416 self._changectx = changectx
417 if fileid is not None:
417 if fileid is not None:
418 self._fileid = fileid
418 self._fileid = fileid
419
419
420 @propertycache
420 @propertycache
421 def _changectx(self):
421 def _changectx(self):
422 try:
422 try:
423 return changectx(self._repo, self._changeid)
423 return changectx(self._repo, self._changeid)
424 except error.RepoLookupError:
424 except error.RepoLookupError:
425 # Linkrev may point to any revision in the repository. When the
425 # Linkrev may point to any revision in the repository. When the
426 # repository is filtered this may lead to `filectx` trying to build
426 # repository is filtered this may lead to `filectx` trying to build
427 # `changectx` for filtered revision. In such case we fallback to
427 # `changectx` for filtered revision. In such case we fallback to
428 # creating `changectx` on the unfiltered version of the reposition.
428 # creating `changectx` on the unfiltered version of the reposition.
429 # This fallback should not be an issue because`changectx` from
429 # This fallback should not be an issue because`changectx` from
430 # `filectx` are not used in complexe operation that care about
430 # `filectx` are not used in complexe operation that care about
431 # filtering.
431 # filtering.
432 #
432 #
433 # This fallback is a cheap and dirty fix that prevent several
433 # This fallback is a cheap and dirty fix that prevent several
434 # crash. It does not ensure the behavior is correct. However the
434 # crash. It does not ensure the behavior is correct. However the
435 # behavior was not correct before filtering either and "incorrect
435 # behavior was not correct before filtering either and "incorrect
436 # behavior" is seen as better as "crash"
436 # behavior" is seen as better as "crash"
437 #
437 #
438 # Linkrevs have several serious troubles with filtering that are
438 # Linkrevs have several serious troubles with filtering that are
439 # complicated to solve. Proper handling of the issue here should be
439 # complicated to solve. Proper handling of the issue here should be
440 # considered when solving linkrev issue are on the table.
440 # considered when solving linkrev issue are on the table.
441 return changectx(self._repo.unfiltered(), self._changeid)
441 return changectx(self._repo.unfiltered(), self._changeid)
442
442
443 @propertycache
443 @propertycache
444 def _filelog(self):
444 def _filelog(self):
445 return self._repo.file(self._path)
445 return self._repo.file(self._path)
446
446
447 @propertycache
447 @propertycache
448 def _changeid(self):
448 def _changeid(self):
449 if '_changectx' in self.__dict__:
449 if '_changectx' in self.__dict__:
450 return self._changectx.rev()
450 return self._changectx.rev()
451 else:
451 else:
452 return self._filelog.linkrev(self._filerev)
452 return self._filelog.linkrev(self._filerev)
453
453
454 @propertycache
454 @propertycache
455 def _filenode(self):
455 def _filenode(self):
456 if '_fileid' in self.__dict__:
456 if '_fileid' in self.__dict__:
457 return self._filelog.lookup(self._fileid)
457 return self._filelog.lookup(self._fileid)
458 else:
458 else:
459 return self._changectx.filenode(self._path)
459 return self._changectx.filenode(self._path)
460
460
461 @propertycache
461 @propertycache
462 def _filerev(self):
462 def _filerev(self):
463 return self._filelog.rev(self._filenode)
463 return self._filelog.rev(self._filenode)
464
464
465 @propertycache
465 @propertycache
466 def _repopath(self):
466 def _repopath(self):
467 return self._path
467 return self._path
468
468
469 def __nonzero__(self):
469 def __nonzero__(self):
470 try:
470 try:
471 self._filenode
471 self._filenode
472 return True
472 return True
473 except error.LookupError:
473 except error.LookupError:
474 # file is missing
474 # file is missing
475 return False
475 return False
476
476
477 def __str__(self):
477 def __str__(self):
478 return "%s@%s" % (self.path(), short(self.node()))
478 return "%s@%s" % (self.path(), short(self.node()))
479
479
480 def __repr__(self):
480 def __repr__(self):
481 return "<filectx %s>" % str(self)
481 return "<filectx %s>" % str(self)
482
482
483 def __hash__(self):
483 def __hash__(self):
484 try:
484 try:
485 return hash((self._path, self._filenode))
485 return hash((self._path, self._filenode))
486 except AttributeError:
486 except AttributeError:
487 return id(self)
487 return id(self)
488
488
489 def __eq__(self, other):
489 def __eq__(self, other):
490 try:
490 try:
491 return (self._path == other._path
491 return (self._path == other._path
492 and self._filenode == other._filenode)
492 and self._filenode == other._filenode)
493 except AttributeError:
493 except AttributeError:
494 return False
494 return False
495
495
496 def __ne__(self, other):
496 def __ne__(self, other):
497 return not (self == other)
497 return not (self == other)
498
498
499 def filectx(self, fileid):
499 def filectx(self, fileid):
500 '''opens an arbitrary revision of the file without
500 '''opens an arbitrary revision of the file without
501 opening a new filelog'''
501 opening a new filelog'''
502 return filectx(self._repo, self._path, fileid=fileid,
502 return filectx(self._repo, self._path, fileid=fileid,
503 filelog=self._filelog)
503 filelog=self._filelog)
504
504
505 def filerev(self):
505 def filerev(self):
506 return self._filerev
506 return self._filerev
507 def filenode(self):
507 def filenode(self):
508 return self._filenode
508 return self._filenode
509 def flags(self):
509 def flags(self):
510 return self._changectx.flags(self._path)
510 return self._changectx.flags(self._path)
511 def filelog(self):
511 def filelog(self):
512 return self._filelog
512 return self._filelog
513
513
514 def rev(self):
514 def rev(self):
515 if '_changectx' in self.__dict__:
515 if '_changectx' in self.__dict__:
516 return self._changectx.rev()
516 return self._changectx.rev()
517 if '_changeid' in self.__dict__:
517 if '_changeid' in self.__dict__:
518 return self._changectx.rev()
518 return self._changectx.rev()
519 return self._filelog.linkrev(self._filerev)
519 return self._filelog.linkrev(self._filerev)
520
520
521 def linkrev(self):
521 def linkrev(self):
522 return self._filelog.linkrev(self._filerev)
522 return self._filelog.linkrev(self._filerev)
523 def node(self):
523 def node(self):
524 return self._changectx.node()
524 return self._changectx.node()
525 def hex(self):
525 def hex(self):
526 return hex(self.node())
526 return hex(self.node())
527 def user(self):
527 def user(self):
528 return self._changectx.user()
528 return self._changectx.user()
529 def date(self):
529 def date(self):
530 return self._changectx.date()
530 return self._changectx.date()
531 def files(self):
531 def files(self):
532 return self._changectx.files()
532 return self._changectx.files()
533 def description(self):
533 def description(self):
534 return self._changectx.description()
534 return self._changectx.description()
535 def branch(self):
535 def branch(self):
536 return self._changectx.branch()
536 return self._changectx.branch()
537 def extra(self):
537 def extra(self):
538 return self._changectx.extra()
538 return self._changectx.extra()
539 def phase(self):
539 def phase(self):
540 return self._changectx.phase()
540 return self._changectx.phase()
541 def phasestr(self):
541 def phasestr(self):
542 return self._changectx.phasestr()
542 return self._changectx.phasestr()
543 def manifest(self):
543 def manifest(self):
544 return self._changectx.manifest()
544 return self._changectx.manifest()
545 def changectx(self):
545 def changectx(self):
546 return self._changectx
546 return self._changectx
547
547
548 def data(self):
548 def data(self):
549 return self._filelog.read(self._filenode)
549 return self._filelog.read(self._filenode)
550 def path(self):
550 def path(self):
551 return self._path
551 return self._path
552 def size(self):
552 def size(self):
553 return self._filelog.size(self._filerev)
553 return self._filelog.size(self._filerev)
554
554
555 def isbinary(self):
555 def isbinary(self):
556 try:
556 try:
557 return util.binary(self.data())
557 return util.binary(self.data())
558 except IOError:
558 except IOError:
559 return False
559 return False
560
560
561 def cmp(self, fctx):
561 def cmp(self, fctx):
562 """compare with other file context
562 """compare with other file context
563
563
564 returns True if different than fctx.
564 returns True if different than fctx.
565 """
565 """
566 if (fctx._filerev is None
566 if (fctx._filerev is None
567 and (self._repo._encodefilterpats
567 and (self._repo._encodefilterpats
568 # if file data starts with '\1\n', empty metadata block is
568 # if file data starts with '\1\n', empty metadata block is
569 # prepended, which adds 4 bytes to filelog.size().
569 # prepended, which adds 4 bytes to filelog.size().
570 or self.size() - 4 == fctx.size())
570 or self.size() - 4 == fctx.size())
571 or self.size() == fctx.size()):
571 or self.size() == fctx.size()):
572 return self._filelog.cmp(self._filenode, fctx.data())
572 return self._filelog.cmp(self._filenode, fctx.data())
573
573
574 return True
574 return True
575
575
576 def renamed(self):
576 def renamed(self):
577 """check if file was actually renamed in this changeset revision
577 """check if file was actually renamed in this changeset revision
578
578
579 If rename logged in file revision, we report copy for changeset only
579 If rename logged in file revision, we report copy for changeset only
580 if file revisions linkrev points back to the changeset in question
580 if file revisions linkrev points back to the changeset in question
581 or both changeset parents contain different file revisions.
581 or both changeset parents contain different file revisions.
582 """
582 """
583
583
584 renamed = self._filelog.renamed(self._filenode)
584 renamed = self._filelog.renamed(self._filenode)
585 if not renamed:
585 if not renamed:
586 return renamed
586 return renamed
587
587
588 if self.rev() == self.linkrev():
588 if self.rev() == self.linkrev():
589 return renamed
589 return renamed
590
590
591 name = self.path()
591 name = self.path()
592 fnode = self._filenode
592 fnode = self._filenode
593 for p in self._changectx.parents():
593 for p in self._changectx.parents():
594 try:
594 try:
595 if fnode == p.filenode(name):
595 if fnode == p.filenode(name):
596 return None
596 return None
597 except error.LookupError:
597 except error.LookupError:
598 pass
598 pass
599 return renamed
599 return renamed
600
600
601 def parents(self):
601 def parents(self):
602 p = self._path
602 p = self._path
603 fl = self._filelog
603 fl = self._filelog
604 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
604 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
605
605
606 r = self._filelog.renamed(self._filenode)
606 r = self._filelog.renamed(self._filenode)
607 if r:
607 if r:
608 pl[0] = (r[0], r[1], None)
608 pl[0] = (r[0], r[1], None)
609
609
610 return [filectx(self._repo, p, fileid=n, filelog=l)
610 return [filectx(self._repo, p, fileid=n, filelog=l)
611 for p, n, l in pl if n != nullid]
611 for p, n, l in pl if n != nullid]
612
612
613 def p1(self):
613 def p1(self):
614 return self.parents()[0]
614 return self.parents()[0]
615
615
616 def p2(self):
616 def p2(self):
617 p = self.parents()
617 p = self.parents()
618 if len(p) == 2:
618 if len(p) == 2:
619 return p[1]
619 return p[1]
620 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
620 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
621
621
622 def children(self):
622 def children(self):
623 # hard for renames
623 # hard for renames
624 c = self._filelog.children(self._filenode)
624 c = self._filelog.children(self._filenode)
625 return [filectx(self._repo, self._path, fileid=x,
625 return [filectx(self._repo, self._path, fileid=x,
626 filelog=self._filelog) for x in c]
626 filelog=self._filelog) for x in c]
627
627
628 def annotate(self, follow=False, linenumber=None, diffopts=None):
628 def annotate(self, follow=False, linenumber=None, diffopts=None):
629 '''returns a list of tuples of (ctx, line) for each line
629 '''returns a list of tuples of (ctx, line) for each line
630 in the file, where ctx is the filectx of the node where
630 in the file, where ctx is the filectx of the node where
631 that line was last changed.
631 that line was last changed.
632 This returns tuples of ((ctx, linenumber), line) for each line,
632 This returns tuples of ((ctx, linenumber), line) for each line,
633 if "linenumber" parameter is NOT "None".
633 if "linenumber" parameter is NOT "None".
634 In such tuples, linenumber means one at the first appearance
634 In such tuples, linenumber means one at the first appearance
635 in the managed file.
635 in the managed file.
636 To reduce annotation cost,
636 To reduce annotation cost,
637 this returns fixed value(False is used) as linenumber,
637 this returns fixed value(False is used) as linenumber,
638 if "linenumber" parameter is "False".'''
638 if "linenumber" parameter is "False".'''
639
639
640 def decorate_compat(text, rev):
640 def decorate_compat(text, rev):
641 return ([rev] * len(text.splitlines()), text)
641 return ([rev] * len(text.splitlines()), text)
642
642
643 def without_linenumber(text, rev):
643 def without_linenumber(text, rev):
644 return ([(rev, False)] * len(text.splitlines()), text)
644 return ([(rev, False)] * len(text.splitlines()), text)
645
645
646 def with_linenumber(text, rev):
646 def with_linenumber(text, rev):
647 size = len(text.splitlines())
647 size = len(text.splitlines())
648 return ([(rev, i) for i in xrange(1, size + 1)], text)
648 return ([(rev, i) for i in xrange(1, size + 1)], text)
649
649
650 decorate = (((linenumber is None) and decorate_compat) or
650 decorate = (((linenumber is None) and decorate_compat) or
651 (linenumber and with_linenumber) or
651 (linenumber and with_linenumber) or
652 without_linenumber)
652 without_linenumber)
653
653
654 def pair(parent, child):
654 def pair(parent, child):
655 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
655 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
656 refine=True)
656 refine=True)
657 for (a1, a2, b1, b2), t in blocks:
657 for (a1, a2, b1, b2), t in blocks:
658 # Changed blocks ('!') or blocks made only of blank lines ('~')
658 # Changed blocks ('!') or blocks made only of blank lines ('~')
659 # belong to the child.
659 # belong to the child.
660 if t == '=':
660 if t == '=':
661 child[0][b1:b2] = parent[0][a1:a2]
661 child[0][b1:b2] = parent[0][a1:a2]
662 return child
662 return child
663
663
664 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
664 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
665 def getctx(path, fileid):
665 def getctx(path, fileid):
666 log = path == self._path and self._filelog or getlog(path)
666 log = path == self._path and self._filelog or getlog(path)
667 return filectx(self._repo, path, fileid=fileid, filelog=log)
667 return filectx(self._repo, path, fileid=fileid, filelog=log)
668 getctx = util.lrucachefunc(getctx)
668 getctx = util.lrucachefunc(getctx)
669
669
670 def parents(f):
670 def parents(f):
671 # we want to reuse filectx objects as much as possible
671 # we want to reuse filectx objects as much as possible
672 p = f._path
672 p = f._path
673 if f._filerev is None: # working dir
673 if f._filerev is None: # working dir
674 pl = [(n.path(), n.filerev()) for n in f.parents()]
674 pl = [(n.path(), n.filerev()) for n in f.parents()]
675 else:
675 else:
676 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
676 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
677
677
678 if follow:
678 if follow:
679 r = f.renamed()
679 r = f.renamed()
680 if r:
680 if r:
681 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
681 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
682
682
683 return [getctx(p, n) for p, n in pl if n != nullrev]
683 return [getctx(p, n) for p, n in pl if n != nullrev]
684
684
685 # use linkrev to find the first changeset where self appeared
685 # use linkrev to find the first changeset where self appeared
686 if self.rev() != self.linkrev():
686 if self.rev() != self.linkrev():
687 base = self.filectx(self.filerev())
687 base = self.filectx(self.filerev())
688 else:
688 else:
689 base = self
689 base = self
690
690
691 # This algorithm would prefer to be recursive, but Python is a
691 # This algorithm would prefer to be recursive, but Python is a
692 # bit recursion-hostile. Instead we do an iterative
692 # bit recursion-hostile. Instead we do an iterative
693 # depth-first search.
693 # depth-first search.
694
694
695 visit = [base]
695 visit = [base]
696 hist = {}
696 hist = {}
697 pcache = {}
697 pcache = {}
698 needed = {base: 1}
698 needed = {base: 1}
699 while visit:
699 while visit:
700 f = visit[-1]
700 f = visit[-1]
701 if f not in pcache:
701 if f not in pcache:
702 pcache[f] = parents(f)
702 pcache[f] = parents(f)
703
703
704 ready = True
704 ready = True
705 pl = pcache[f]
705 pl = pcache[f]
706 for p in pl:
706 for p in pl:
707 if p not in hist:
707 if p not in hist:
708 ready = False
708 ready = False
709 visit.append(p)
709 visit.append(p)
710 needed[p] = needed.get(p, 0) + 1
710 needed[p] = needed.get(p, 0) + 1
711 if ready:
711 if ready:
712 visit.pop()
712 visit.pop()
713 curr = decorate(f.data(), f)
713 curr = decorate(f.data(), f)
714 for p in pl:
714 for p in pl:
715 curr = pair(hist[p], curr)
715 curr = pair(hist[p], curr)
716 if needed[p] == 1:
716 if needed[p] == 1:
717 del hist[p]
717 del hist[p]
718 else:
718 else:
719 needed[p] -= 1
719 needed[p] -= 1
720
720
721 hist[f] = curr
721 hist[f] = curr
722 pcache[f] = []
722 pcache[f] = []
723
723
724 return zip(hist[base][0], hist[base][1].splitlines(True))
724 return zip(hist[base][0], hist[base][1].splitlines(True))
725
725
726 def ancestor(self, fc2, actx):
726 def ancestor(self, fc2, actx):
727 """
727 """
728 find the common ancestor file context, if any, of self, and fc2
728 find the common ancestor file context, if any, of self, and fc2
729
729
730 actx must be the changectx of the common ancestor
730 actx must be the changectx of the common ancestor
731 of self's and fc2's respective changesets.
731 of self's and fc2's respective changesets.
732 """
732 """
733
733
734 # the easy case: no (relevant) renames
734 # the easy case: no (relevant) renames
735 if fc2.path() == self.path() and self.path() in actx:
735 if fc2.path() == self.path() and self.path() in actx:
736 return actx[self.path()]
736 return actx[self.path()]
737
737
738 # the next easiest cases: unambiguous predecessor (name trumps
738 # the next easiest cases: unambiguous predecessor (name trumps
739 # history)
739 # history)
740 if self.path() in actx and fc2.path() not in actx:
740 if self.path() in actx and fc2.path() not in actx:
741 return actx[self.path()]
741 return actx[self.path()]
742 if fc2.path() in actx and self.path() not in actx:
742 if fc2.path() in actx and self.path() not in actx:
743 return actx[fc2.path()]
743 return actx[fc2.path()]
744
744
745 # prime the ancestor cache for the working directory
745 # prime the ancestor cache for the working directory
746 acache = {}
746 acache = {}
747 for c in (self, fc2):
747 for c in (self, fc2):
748 if c._filerev is None:
748 if c._filerev is None:
749 pl = [(n.path(), n.filenode()) for n in c.parents()]
749 pl = [(n.path(), n.filenode()) for n in c.parents()]
750 acache[(c._path, None)] = pl
750 acache[(c._path, None)] = pl
751
751
752 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
752 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
753 def parents(vertex):
753 def parents(vertex):
754 if vertex in acache:
754 if vertex in acache:
755 return acache[vertex]
755 return acache[vertex]
756 f, n = vertex
756 f, n = vertex
757 if f not in flcache:
757 if f not in flcache:
758 flcache[f] = self._repo.file(f)
758 flcache[f] = self._repo.file(f)
759 fl = flcache[f]
759 fl = flcache[f]
760 pl = [(f, p) for p in fl.parents(n) if p != nullid]
760 pl = [(f, p) for p in fl.parents(n) if p != nullid]
761 re = fl.renamed(n)
761 re = fl.renamed(n)
762 if re:
762 if re:
763 pl.append(re)
763 pl.append(re)
764 acache[vertex] = pl
764 acache[vertex] = pl
765 return pl
765 return pl
766
766
767 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
767 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
768 v = ancestor.ancestor(a, b, parents)
768 v = ancestor.ancestor(a, b, parents)
769 if v:
769 if v:
770 f, n = v
770 f, n = v
771 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
771 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
772
772
773 return None
773 return None
774
774
775 def ancestors(self, followfirst=False):
775 def ancestors(self, followfirst=False):
776 visit = {}
776 visit = {}
777 c = self
777 c = self
778 cut = followfirst and 1 or None
778 cut = followfirst and 1 or None
779 while True:
779 while True:
780 for parent in c.parents()[:cut]:
780 for parent in c.parents()[:cut]:
781 visit[(parent.rev(), parent.node())] = parent
781 visit[(parent.rev(), parent.node())] = parent
782 if not visit:
782 if not visit:
783 break
783 break
784 c = visit.pop(max(visit))
784 c = visit.pop(max(visit))
785 yield c
785 yield c
786
786
787 def copies(self, c2):
787 def copies(self, c2):
788 if not util.safehasattr(self, "_copycache"):
788 if not util.safehasattr(self, "_copycache"):
789 self._copycache = {}
789 self._copycache = {}
790 sc2 = str(c2)
790 sc2 = str(c2)
791 if sc2 not in self._copycache:
791 if sc2 not in self._copycache:
792 self._copycache[sc2] = copies.pathcopies(c2)
792 self._copycache[sc2] = copies.pathcopies(c2)
793 return self._copycache[sc2]
793 return self._copycache[sc2]
794
794
795 class workingctx(changectx):
795 class workingctx(changectx):
796 """A workingctx object makes access to data related to
796 """A workingctx object makes access to data related to
797 the current working directory convenient.
797 the current working directory convenient.
798 date - any valid date string or (unixtime, offset), or None.
798 date - any valid date string or (unixtime, offset), or None.
799 user - username string, or None.
799 user - username string, or None.
800 extra - a dictionary of extra values, or None.
800 extra - a dictionary of extra values, or None.
801 changes - a list of file lists as returned by localrepo.status()
801 changes - a list of file lists as returned by localrepo.status()
802 or None to use the repository status.
802 or None to use the repository status.
803 """
803 """
804 def __init__(self, repo, text="", user=None, date=None, extra=None,
804 def __init__(self, repo, text="", user=None, date=None, extra=None,
805 changes=None):
805 changes=None):
806 self._repo = repo
806 self._repo = repo
807 self._rev = None
807 self._rev = None
808 self._node = None
808 self._node = None
809 self._text = text
809 self._text = text
810 if date:
810 if date:
811 self._date = util.parsedate(date)
811 self._date = util.parsedate(date)
812 if user:
812 if user:
813 self._user = user
813 self._user = user
814 if changes:
814 if changes:
815 self._status = list(changes[:4])
815 self._status = list(changes[:4])
816 self._unknown = changes[4]
816 self._unknown = changes[4]
817 self._ignored = changes[5]
817 self._ignored = changes[5]
818 self._clean = changes[6]
818 self._clean = changes[6]
819 else:
819 else:
820 self._unknown = None
820 self._unknown = None
821 self._ignored = None
821 self._ignored = None
822 self._clean = None
822 self._clean = None
823
823
824 self._extra = {}
824 self._extra = {}
825 if extra:
825 if extra:
826 self._extra = extra.copy()
826 self._extra = extra.copy()
827 if 'branch' not in self._extra:
827 if 'branch' not in self._extra:
828 try:
828 try:
829 branch = encoding.fromlocal(self._repo.dirstate.branch())
829 branch = encoding.fromlocal(self._repo.dirstate.branch())
830 except UnicodeDecodeError:
830 except UnicodeDecodeError:
831 raise util.Abort(_('branch name not in UTF-8!'))
831 raise util.Abort(_('branch name not in UTF-8!'))
832 self._extra['branch'] = branch
832 self._extra['branch'] = branch
833 if self._extra['branch'] == '':
833 if self._extra['branch'] == '':
834 self._extra['branch'] = 'default'
834 self._extra['branch'] = 'default'
835
835
836 def __str__(self):
836 def __str__(self):
837 return str(self._parents[0]) + "+"
837 return str(self._parents[0]) + "+"
838
838
839 def __repr__(self):
839 def __repr__(self):
840 return "<workingctx %s>" % str(self)
840 return "<workingctx %s>" % str(self)
841
841
842 def __nonzero__(self):
842 def __nonzero__(self):
843 return True
843 return True
844
844
845 def __contains__(self, key):
845 def __contains__(self, key):
846 return self._repo.dirstate[key] not in "?r"
846 return self._repo.dirstate[key] not in "?r"
847
847
848 def _buildflagfunc(self):
848 def _buildflagfunc(self):
849 # Create a fallback function for getting file flags when the
849 # Create a fallback function for getting file flags when the
850 # filesystem doesn't support them
850 # filesystem doesn't support them
851
851
852 copiesget = self._repo.dirstate.copies().get
852 copiesget = self._repo.dirstate.copies().get
853
853
854 if len(self._parents) < 2:
854 if len(self._parents) < 2:
855 # when we have one parent, it's easy: copy from parent
855 # when we have one parent, it's easy: copy from parent
856 man = self._parents[0].manifest()
856 man = self._parents[0].manifest()
857 def func(f):
857 def func(f):
858 f = copiesget(f, f)
858 f = copiesget(f, f)
859 return man.flags(f)
859 return man.flags(f)
860 else:
860 else:
861 # merges are tricky: we try to reconstruct the unstored
861 # merges are tricky: we try to reconstruct the unstored
862 # result from the merge (issue1802)
862 # result from the merge (issue1802)
863 p1, p2 = self._parents
863 p1, p2 = self._parents
864 pa = p1.ancestor(p2)
864 pa = p1.ancestor(p2)
865 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
865 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
866
866
867 def func(f):
867 def func(f):
868 f = copiesget(f, f) # may be wrong for merges with copies
868 f = copiesget(f, f) # may be wrong for merges with copies
869 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
869 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
870 if fl1 == fl2:
870 if fl1 == fl2:
871 return fl1
871 return fl1
872 if fl1 == fla:
872 if fl1 == fla:
873 return fl2
873 return fl2
874 if fl2 == fla:
874 if fl2 == fla:
875 return fl1
875 return fl1
876 return '' # punt for conflicts
876 return '' # punt for conflicts
877
877
878 return func
878 return func
879
879
880 @propertycache
880 @propertycache
881 def _flagfunc(self):
881 def _flagfunc(self):
882 return self._repo.dirstate.flagfunc(self._buildflagfunc)
882 return self._repo.dirstate.flagfunc(self._buildflagfunc)
883
883
884 @propertycache
884 @propertycache
885 def _manifest(self):
885 def _manifest(self):
886 """generate a manifest corresponding to the working directory"""
886 """generate a manifest corresponding to the working directory"""
887
887
888 man = self._parents[0].manifest().copy()
888 man = self._parents[0].manifest().copy()
889 if len(self._parents) > 1:
889 if len(self._parents) > 1:
890 man2 = self.p2().manifest()
890 man2 = self.p2().manifest()
891 def getman(f):
891 def getman(f):
892 if f in man:
892 if f in man:
893 return man
893 return man
894 return man2
894 return man2
895 else:
895 else:
896 getman = lambda f: man
896 getman = lambda f: man
897
897
898 copied = self._repo.dirstate.copies()
898 copied = self._repo.dirstate.copies()
899 ff = self._flagfunc
899 ff = self._flagfunc
900 modified, added, removed, deleted = self._status
900 modified, added, removed, deleted = self._status
901 for i, l in (("a", added), ("m", modified)):
901 for i, l in (("a", added), ("m", modified)):
902 for f in l:
902 for f in l:
903 orig = copied.get(f, f)
903 orig = copied.get(f, f)
904 man[f] = getman(orig).get(orig, nullid) + i
904 man[f] = getman(orig).get(orig, nullid) + i
905 try:
905 try:
906 man.set(f, ff(f))
906 man.set(f, ff(f))
907 except OSError:
907 except OSError:
908 pass
908 pass
909
909
910 for f in deleted + removed:
910 for f in deleted + removed:
911 if f in man:
911 if f in man:
912 del man[f]
912 del man[f]
913
913
914 return man
914 return man
915
915
916 def __iter__(self):
916 def __iter__(self):
917 d = self._repo.dirstate
917 d = self._repo.dirstate
918 for f in d:
918 for f in d:
919 if d[f] != 'r':
919 if d[f] != 'r':
920 yield f
920 yield f
921
921
922 @propertycache
922 @propertycache
923 def _status(self):
923 def _status(self):
924 return self._repo.status()[:4]
924 return self._repo.status()[:4]
925
925
926 @propertycache
926 @propertycache
927 def _user(self):
927 def _user(self):
928 return self._repo.ui.username()
928 return self._repo.ui.username()
929
929
930 @propertycache
930 @propertycache
931 def _date(self):
931 def _date(self):
932 return util.makedate()
932 return util.makedate()
933
933
934 @propertycache
934 @propertycache
935 def _parents(self):
935 def _parents(self):
936 p = self._repo.dirstate.parents()
936 p = self._repo.dirstate.parents()
937 if p[1] == nullid:
937 if p[1] == nullid:
938 p = p[:-1]
938 p = p[:-1]
939 return [changectx(self._repo, x) for x in p]
939 return [changectx(self._repo, x) for x in p]
940
940
941 def status(self, ignored=False, clean=False, unknown=False):
941 def status(self, ignored=False, clean=False, unknown=False):
942 """Explicit status query
942 """Explicit status query
943 Unless this method is used to query the working copy status, the
943 Unless this method is used to query the working copy status, the
944 _status property will implicitly read the status using its default
944 _status property will implicitly read the status using its default
945 arguments."""
945 arguments."""
946 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
946 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
947 self._unknown = self._ignored = self._clean = None
947 self._unknown = self._ignored = self._clean = None
948 if unknown:
948 if unknown:
949 self._unknown = stat[4]
949 self._unknown = stat[4]
950 if ignored:
950 if ignored:
951 self._ignored = stat[5]
951 self._ignored = stat[5]
952 if clean:
952 if clean:
953 self._clean = stat[6]
953 self._clean = stat[6]
954 self._status = stat[:4]
954 self._status = stat[:4]
955 return stat
955 return stat
956
956
957 def manifest(self):
957 def manifest(self):
958 return self._manifest
958 return self._manifest
959 def user(self):
959 def user(self):
960 return self._user or self._repo.ui.username()
960 return self._user or self._repo.ui.username()
961 def date(self):
961 def date(self):
962 return self._date
962 return self._date
963 def description(self):
963 def description(self):
964 return self._text
964 return self._text
965 def files(self):
965 def files(self):
966 return sorted(self._status[0] + self._status[1] + self._status[2])
966 return sorted(self._status[0] + self._status[1] + self._status[2])
967
967
968 def modified(self):
968 def modified(self):
969 return self._status[0]
969 return self._status[0]
970 def added(self):
970 def added(self):
971 return self._status[1]
971 return self._status[1]
972 def removed(self):
972 def removed(self):
973 return self._status[2]
973 return self._status[2]
974 def deleted(self):
974 def deleted(self):
975 return self._status[3]
975 return self._status[3]
976 def unknown(self):
976 def unknown(self):
977 assert self._unknown is not None # must call status first
977 assert self._unknown is not None # must call status first
978 return self._unknown
978 return self._unknown
979 def ignored(self):
979 def ignored(self):
980 assert self._ignored is not None # must call status first
980 assert self._ignored is not None # must call status first
981 return self._ignored
981 return self._ignored
982 def clean(self):
982 def clean(self):
983 assert self._clean is not None # must call status first
983 assert self._clean is not None # must call status first
984 return self._clean
984 return self._clean
985 def branch(self):
985 def branch(self):
986 return encoding.tolocal(self._extra['branch'])
986 return encoding.tolocal(self._extra['branch'])
987 def closesbranch(self):
987 def closesbranch(self):
988 return 'close' in self._extra
988 return 'close' in self._extra
989 def extra(self):
989 def extra(self):
990 return self._extra
990 return self._extra
991
991
992 def tags(self):
992 def tags(self):
993 t = []
993 t = []
994 for p in self.parents():
994 for p in self.parents():
995 t.extend(p.tags())
995 t.extend(p.tags())
996 return t
996 return t
997
997
998 def bookmarks(self):
998 def bookmarks(self):
999 b = []
999 b = []
1000 for p in self.parents():
1000 for p in self.parents():
1001 b.extend(p.bookmarks())
1001 b.extend(p.bookmarks())
1002 return b
1002 return b
1003
1003
1004 def phase(self):
1004 def phase(self):
1005 phase = phases.draft # default phase to draft
1005 phase = phases.draft # default phase to draft
1006 for p in self.parents():
1006 for p in self.parents():
1007 phase = max(phase, p.phase())
1007 phase = max(phase, p.phase())
1008 return phase
1008 return phase
1009
1009
1010 def hidden(self):
1010 def hidden(self):
1011 return False
1011 return False
1012
1012
1013 def children(self):
1013 def children(self):
1014 return []
1014 return []
1015
1015
1016 def flags(self, path):
1016 def flags(self, path):
1017 if '_manifest' in self.__dict__:
1017 if '_manifest' in self.__dict__:
1018 try:
1018 try:
1019 return self._manifest.flags(path)
1019 return self._manifest.flags(path)
1020 except KeyError:
1020 except KeyError:
1021 return ''
1021 return ''
1022
1022
1023 try:
1023 try:
1024 return self._flagfunc(path)
1024 return self._flagfunc(path)
1025 except OSError:
1025 except OSError:
1026 return ''
1026 return ''
1027
1027
1028 def filectx(self, path, filelog=None):
1028 def filectx(self, path, filelog=None):
1029 """get a file context from the working directory"""
1029 """get a file context from the working directory"""
1030 return workingfilectx(self._repo, path, workingctx=self,
1030 return workingfilectx(self._repo, path, workingctx=self,
1031 filelog=filelog)
1031 filelog=filelog)
1032
1032
1033 def ancestor(self, c2):
1033 def ancestor(self, c2):
1034 """return the ancestor context of self and c2"""
1034 """return the ancestor context of self and c2"""
1035 return self._parents[0].ancestor(c2) # punt on two parents for now
1035 return self._parents[0].ancestor(c2) # punt on two parents for now
1036
1036
1037 def walk(self, match):
1037 def walk(self, match):
1038 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1038 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1039 True, False))
1039 True, False))
1040
1040
1041 def dirty(self, missing=False, merge=True, branch=True):
1041 def dirty(self, missing=False, merge=True, branch=True):
1042 "check whether a working directory is modified"
1042 "check whether a working directory is modified"
1043 # check subrepos first
1043 # check subrepos first
1044 for s in sorted(self.substate):
1044 for s in sorted(self.substate):
1045 if self.sub(s).dirty():
1045 if self.sub(s).dirty():
1046 return True
1046 return True
1047 # check current working dir
1047 # check current working dir
1048 return ((merge and self.p2()) or
1048 return ((merge and self.p2()) or
1049 (branch and self.branch() != self.p1().branch()) or
1049 (branch and self.branch() != self.p1().branch()) or
1050 self.modified() or self.added() or self.removed() or
1050 self.modified() or self.added() or self.removed() or
1051 (missing and self.deleted()))
1051 (missing and self.deleted()))
1052
1052
1053 def add(self, list, prefix=""):
1053 def add(self, list, prefix=""):
1054 join = lambda f: os.path.join(prefix, f)
1054 join = lambda f: os.path.join(prefix, f)
1055 wlock = self._repo.wlock()
1055 wlock = self._repo.wlock()
1056 ui, ds = self._repo.ui, self._repo.dirstate
1056 ui, ds = self._repo.ui, self._repo.dirstate
1057 try:
1057 try:
1058 rejected = []
1058 rejected = []
1059 for f in list:
1059 for f in list:
1060 scmutil.checkportable(ui, join(f))
1060 scmutil.checkportable(ui, join(f))
1061 p = self._repo.wjoin(f)
1061 p = self._repo.wjoin(f)
1062 try:
1062 try:
1063 st = os.lstat(p)
1063 st = os.lstat(p)
1064 except OSError:
1064 except OSError:
1065 ui.warn(_("%s does not exist!\n") % join(f))
1065 ui.warn(_("%s does not exist!\n") % join(f))
1066 rejected.append(f)
1066 rejected.append(f)
1067 continue
1067 continue
1068 if st.st_size > 10000000:
1068 if st.st_size > 10000000:
1069 ui.warn(_("%s: up to %d MB of RAM may be required "
1069 ui.warn(_("%s: up to %d MB of RAM may be required "
1070 "to manage this file\n"
1070 "to manage this file\n"
1071 "(use 'hg revert %s' to cancel the "
1071 "(use 'hg revert %s' to cancel the "
1072 "pending addition)\n")
1072 "pending addition)\n")
1073 % (f, 3 * st.st_size // 1000000, join(f)))
1073 % (f, 3 * st.st_size // 1000000, join(f)))
1074 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1074 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1075 ui.warn(_("%s not added: only files and symlinks "
1075 ui.warn(_("%s not added: only files and symlinks "
1076 "supported currently\n") % join(f))
1076 "supported currently\n") % join(f))
1077 rejected.append(p)
1077 rejected.append(p)
1078 elif ds[f] in 'amn':
1078 elif ds[f] in 'amn':
1079 ui.warn(_("%s already tracked!\n") % join(f))
1079 ui.warn(_("%s already tracked!\n") % join(f))
1080 elif ds[f] == 'r':
1080 elif ds[f] == 'r':
1081 ds.normallookup(f)
1081 ds.normallookup(f)
1082 else:
1082 else:
1083 ds.add(f)
1083 ds.add(f)
1084 return rejected
1084 return rejected
1085 finally:
1085 finally:
1086 wlock.release()
1086 wlock.release()
1087
1087
1088 def forget(self, files, prefix=""):
1088 def forget(self, files, prefix=""):
1089 join = lambda f: os.path.join(prefix, f)
1089 join = lambda f: os.path.join(prefix, f)
1090 wlock = self._repo.wlock()
1090 wlock = self._repo.wlock()
1091 try:
1091 try:
1092 rejected = []
1092 rejected = []
1093 for f in files:
1093 for f in files:
1094 if f not in self._repo.dirstate:
1094 if f not in self._repo.dirstate:
1095 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1095 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1096 rejected.append(f)
1096 rejected.append(f)
1097 elif self._repo.dirstate[f] != 'a':
1097 elif self._repo.dirstate[f] != 'a':
1098 self._repo.dirstate.remove(f)
1098 self._repo.dirstate.remove(f)
1099 else:
1099 else:
1100 self._repo.dirstate.drop(f)
1100 self._repo.dirstate.drop(f)
1101 return rejected
1101 return rejected
1102 finally:
1102 finally:
1103 wlock.release()
1103 wlock.release()
1104
1104
1105 def ancestors(self):
1105 def ancestors(self):
1106 for a in self._repo.changelog.ancestors(
1106 for a in self._repo.changelog.ancestors(
1107 [p.rev() for p in self._parents]):
1107 [p.rev() for p in self._parents]):
1108 yield changectx(self._repo, a)
1108 yield changectx(self._repo, a)
1109
1109
1110 def undelete(self, list):
1110 def undelete(self, list):
1111 pctxs = self.parents()
1111 pctxs = self.parents()
1112 wlock = self._repo.wlock()
1112 wlock = self._repo.wlock()
1113 try:
1113 try:
1114 for f in list:
1114 for f in list:
1115 if self._repo.dirstate[f] != 'r':
1115 if self._repo.dirstate[f] != 'r':
1116 self._repo.ui.warn(_("%s not removed!\n") % f)
1116 self._repo.ui.warn(_("%s not removed!\n") % f)
1117 else:
1117 else:
1118 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1118 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1119 t = fctx.data()
1119 t = fctx.data()
1120 self._repo.wwrite(f, t, fctx.flags())
1120 self._repo.wwrite(f, t, fctx.flags())
1121 self._repo.dirstate.normal(f)
1121 self._repo.dirstate.normal(f)
1122 finally:
1122 finally:
1123 wlock.release()
1123 wlock.release()
1124
1124
1125 def copy(self, source, dest):
1125 def copy(self, source, dest):
1126 p = self._repo.wjoin(dest)
1126 p = self._repo.wjoin(dest)
1127 if not os.path.lexists(p):
1127 if not os.path.lexists(p):
1128 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1128 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1129 elif not (os.path.isfile(p) or os.path.islink(p)):
1129 elif not (os.path.isfile(p) or os.path.islink(p)):
1130 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1130 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1131 "symbolic link\n") % dest)
1131 "symbolic link\n") % dest)
1132 else:
1132 else:
1133 wlock = self._repo.wlock()
1133 wlock = self._repo.wlock()
1134 try:
1134 try:
1135 if self._repo.dirstate[dest] in '?r':
1135 if self._repo.dirstate[dest] in '?r':
1136 self._repo.dirstate.add(dest)
1136 self._repo.dirstate.add(dest)
1137 self._repo.dirstate.copy(source, dest)
1137 self._repo.dirstate.copy(source, dest)
1138 finally:
1138 finally:
1139 wlock.release()
1139 wlock.release()
1140
1140
1141 def dirs(self):
1141 def dirs(self):
1142 return set(self._repo.dirstate.dirs())
1142 return set(self._repo.dirstate.dirs())
1143
1143
1144 class workingfilectx(filectx):
1144 class workingfilectx(filectx):
1145 """A workingfilectx object makes access to data related to a particular
1145 """A workingfilectx object makes access to data related to a particular
1146 file in the working directory convenient."""
1146 file in the working directory convenient."""
1147 def __init__(self, repo, path, filelog=None, workingctx=None):
1147 def __init__(self, repo, path, filelog=None, workingctx=None):
1148 """changeid can be a changeset revision, node, or tag.
1148 """changeid can be a changeset revision, node, or tag.
1149 fileid can be a file revision or node."""
1149 fileid can be a file revision or node."""
1150 self._repo = repo
1150 self._repo = repo
1151 self._path = path
1151 self._path = path
1152 self._changeid = None
1152 self._changeid = None
1153 self._filerev = self._filenode = None
1153 self._filerev = self._filenode = None
1154
1154
1155 if filelog:
1155 if filelog:
1156 self._filelog = filelog
1156 self._filelog = filelog
1157 if workingctx:
1157 if workingctx:
1158 self._changectx = workingctx
1158 self._changectx = workingctx
1159
1159
1160 @propertycache
1160 @propertycache
1161 def _changectx(self):
1161 def _changectx(self):
1162 return workingctx(self._repo)
1162 return workingctx(self._repo)
1163
1163
1164 def __nonzero__(self):
1164 def __nonzero__(self):
1165 return True
1165 return True
1166
1166
1167 def __str__(self):
1167 def __str__(self):
1168 return "%s@%s" % (self.path(), self._changectx)
1168 return "%s@%s" % (self.path(), self._changectx)
1169
1169
1170 def __repr__(self):
1170 def __repr__(self):
1171 return "<workingfilectx %s>" % str(self)
1171 return "<workingfilectx %s>" % str(self)
1172
1172
1173 def data(self):
1173 def data(self):
1174 return self._repo.wread(self._path)
1174 return self._repo.wread(self._path)
1175 def renamed(self):
1175 def renamed(self):
1176 rp = self._repo.dirstate.copied(self._path)
1176 rp = self._repo.dirstate.copied(self._path)
1177 if not rp:
1177 if not rp:
1178 return None
1178 return None
1179 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1179 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1180
1180
1181 def parents(self):
1181 def parents(self):
1182 '''return parent filectxs, following copies if necessary'''
1182 '''return parent filectxs, following copies if necessary'''
1183 def filenode(ctx, path):
1183 def filenode(ctx, path):
1184 return ctx._manifest.get(path, nullid)
1184 return ctx._manifest.get(path, nullid)
1185
1185
1186 path = self._path
1186 path = self._path
1187 fl = self._filelog
1187 fl = self._filelog
1188 pcl = self._changectx._parents
1188 pcl = self._changectx._parents
1189 renamed = self.renamed()
1189 renamed = self.renamed()
1190
1190
1191 if renamed:
1191 if renamed:
1192 pl = [renamed + (None,)]
1192 pl = [renamed + (None,)]
1193 else:
1193 else:
1194 pl = [(path, filenode(pcl[0], path), fl)]
1194 pl = [(path, filenode(pcl[0], path), fl)]
1195
1195
1196 for pc in pcl[1:]:
1196 for pc in pcl[1:]:
1197 pl.append((path, filenode(pc, path), fl))
1197 pl.append((path, filenode(pc, path), fl))
1198
1198
1199 return [filectx(self._repo, p, fileid=n, filelog=l)
1199 return [filectx(self._repo, p, fileid=n, filelog=l)
1200 for p, n, l in pl if n != nullid]
1200 for p, n, l in pl if n != nullid]
1201
1201
1202 def children(self):
1202 def children(self):
1203 return []
1203 return []
1204
1204
1205 def size(self):
1205 def size(self):
1206 return os.lstat(self._repo.wjoin(self._path)).st_size
1206 return os.lstat(self._repo.wjoin(self._path)).st_size
1207 def date(self):
1207 def date(self):
1208 t, tz = self._changectx.date()
1208 t, tz = self._changectx.date()
1209 try:
1209 try:
1210 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1210 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1211 except OSError, err:
1211 except OSError, err:
1212 if err.errno != errno.ENOENT:
1212 if err.errno != errno.ENOENT:
1213 raise
1213 raise
1214 return (t, tz)
1214 return (t, tz)
1215
1215
1216 def cmp(self, fctx):
1216 def cmp(self, fctx):
1217 """compare with other file context
1217 """compare with other file context
1218
1218
1219 returns True if different than fctx.
1219 returns True if different than fctx.
1220 """
1220 """
1221 # fctx should be a filectx (not a workingfilectx)
1221 # fctx should be a filectx (not a workingfilectx)
1222 # invert comparison to reuse the same code path
1222 # invert comparison to reuse the same code path
1223 return fctx.cmp(self)
1223 return fctx.cmp(self)
1224
1224
1225 class memctx(object):
1225 class memctx(object):
1226 """Use memctx to perform in-memory commits via localrepo.commitctx().
1226 """Use memctx to perform in-memory commits via localrepo.commitctx().
1227
1227
1228 Revision information is supplied at initialization time while
1228 Revision information is supplied at initialization time while
1229 related files data and is made available through a callback
1229 related files data and is made available through a callback
1230 mechanism. 'repo' is the current localrepo, 'parents' is a
1230 mechanism. 'repo' is the current localrepo, 'parents' is a
1231 sequence of two parent revisions identifiers (pass None for every
1231 sequence of two parent revisions identifiers (pass None for every
1232 missing parent), 'text' is the commit message and 'files' lists
1232 missing parent), 'text' is the commit message and 'files' lists
1233 names of files touched by the revision (normalized and relative to
1233 names of files touched by the revision (normalized and relative to
1234 repository root).
1234 repository root).
1235
1235
1236 filectxfn(repo, memctx, path) is a callable receiving the
1236 filectxfn(repo, memctx, path) is a callable receiving the
1237 repository, the current memctx object and the normalized path of
1237 repository, the current memctx object and the normalized path of
1238 requested file, relative to repository root. It is fired by the
1238 requested file, relative to repository root. It is fired by the
1239 commit function for every file in 'files', but calls order is
1239 commit function for every file in 'files', but calls order is
1240 undefined. If the file is available in the revision being
1240 undefined. If the file is available in the revision being
1241 committed (updated or added), filectxfn returns a memfilectx
1241 committed (updated or added), filectxfn returns a memfilectx
1242 object. If the file was removed, filectxfn raises an
1242 object. If the file was removed, filectxfn raises an
1243 IOError. Moved files are represented by marking the source file
1243 IOError. Moved files are represented by marking the source file
1244 removed and the new file added with copy information (see
1244 removed and the new file added with copy information (see
1245 memfilectx).
1245 memfilectx).
1246
1246
1247 user receives the committer name and defaults to current
1247 user receives the committer name and defaults to current
1248 repository username, date is the commit date in any format
1248 repository username, date is the commit date in any format
1249 supported by util.parsedate() and defaults to current date, extra
1249 supported by util.parsedate() and defaults to current date, extra
1250 is a dictionary of metadata or is left empty.
1250 is a dictionary of metadata or is left empty.
1251 """
1251 """
1252 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1252 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1253 date=None, extra=None):
1253 date=None, extra=None):
1254 self._repo = repo
1254 self._repo = repo
1255 self._rev = None
1255 self._rev = None
1256 self._node = None
1256 self._node = None
1257 self._text = text
1257 self._text = text
1258 self._date = date and util.parsedate(date) or util.makedate()
1258 self._date = date and util.parsedate(date) or util.makedate()
1259 self._user = user
1259 self._user = user
1260 parents = [(p or nullid) for p in parents]
1260 parents = [(p or nullid) for p in parents]
1261 p1, p2 = parents
1261 p1, p2 = parents
1262 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1262 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1263 files = sorted(set(files))
1263 files = sorted(set(files))
1264 self._status = [files, [], [], [], []]
1264 self._status = [files, [], [], [], []]
1265 self._filectxfn = filectxfn
1265 self._filectxfn = filectxfn
1266
1266
1267 self._extra = extra and extra.copy() or {}
1267 self._extra = extra and extra.copy() or {}
1268 if self._extra.get('branch', '') == '':
1268 if self._extra.get('branch', '') == '':
1269 self._extra['branch'] = 'default'
1269 self._extra['branch'] = 'default'
1270
1270
1271 def __str__(self):
1271 def __str__(self):
1272 return str(self._parents[0]) + "+"
1272 return str(self._parents[0]) + "+"
1273
1273
1274 def __int__(self):
1274 def __int__(self):
1275 return self._rev
1275 return self._rev
1276
1276
1277 def __nonzero__(self):
1277 def __nonzero__(self):
1278 return True
1278 return True
1279
1279
1280 def __getitem__(self, key):
1280 def __getitem__(self, key):
1281 return self.filectx(key)
1281 return self.filectx(key)
1282
1282
1283 def p1(self):
1283 def p1(self):
1284 return self._parents[0]
1284 return self._parents[0]
1285 def p2(self):
1285 def p2(self):
1286 return self._parents[1]
1286 return self._parents[1]
1287
1287
1288 def user(self):
1288 def user(self):
1289 return self._user or self._repo.ui.username()
1289 return self._user or self._repo.ui.username()
1290 def date(self):
1290 def date(self):
1291 return self._date
1291 return self._date
1292 def description(self):
1292 def description(self):
1293 return self._text
1293 return self._text
1294 def files(self):
1294 def files(self):
1295 return self.modified()
1295 return self.modified()
1296 def modified(self):
1296 def modified(self):
1297 return self._status[0]
1297 return self._status[0]
1298 def added(self):
1298 def added(self):
1299 return self._status[1]
1299 return self._status[1]
1300 def removed(self):
1300 def removed(self):
1301 return self._status[2]
1301 return self._status[2]
1302 def deleted(self):
1302 def deleted(self):
1303 return self._status[3]
1303 return self._status[3]
1304 def unknown(self):
1304 def unknown(self):
1305 return self._status[4]
1305 return self._status[4]
1306 def ignored(self):
1306 def ignored(self):
1307 return self._status[5]
1307 return self._status[5]
1308 def clean(self):
1308 def clean(self):
1309 return self._status[6]
1309 return self._status[6]
1310 def branch(self):
1310 def branch(self):
1311 return encoding.tolocal(self._extra['branch'])
1311 return encoding.tolocal(self._extra['branch'])
1312 def extra(self):
1312 def extra(self):
1313 return self._extra
1313 return self._extra
1314 def flags(self, f):
1314 def flags(self, f):
1315 return self[f].flags()
1315 return self[f].flags()
1316
1316
1317 def parents(self):
1317 def parents(self):
1318 """return contexts for each parent changeset"""
1318 """return contexts for each parent changeset"""
1319 return self._parents
1319 return self._parents
1320
1320
1321 def filectx(self, path, filelog=None):
1321 def filectx(self, path, filelog=None):
1322 """get a file context from the working directory"""
1322 """get a file context from the working directory"""
1323 return self._filectxfn(self._repo, self, path)
1323 return self._filectxfn(self._repo, self, path)
1324
1324
1325 def commit(self):
1325 def commit(self):
1326 """commit context to the repo"""
1326 """commit context to the repo"""
1327 return self._repo.commitctx(self)
1327 return self._repo.commitctx(self)
1328
1328
1329 class memfilectx(object):
1329 class memfilectx(object):
1330 """memfilectx represents an in-memory file to commit.
1330 """memfilectx represents an in-memory file to commit.
1331
1331
1332 See memctx for more details.
1332 See memctx for more details.
1333 """
1333 """
1334 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1334 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1335 """
1335 """
1336 path is the normalized file path relative to repository root.
1336 path is the normalized file path relative to repository root.
1337 data is the file content as a string.
1337 data is the file content as a string.
1338 islink is True if the file is a symbolic link.
1338 islink is True if the file is a symbolic link.
1339 isexec is True if the file is executable.
1339 isexec is True if the file is executable.
1340 copied is the source file path if current file was copied in the
1340 copied is the source file path if current file was copied in the
1341 revision being committed, or None."""
1341 revision being committed, or None."""
1342 self._path = path
1342 self._path = path
1343 self._data = data
1343 self._data = data
1344 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1344 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1345 self._copied = None
1345 self._copied = None
1346 if copied:
1346 if copied:
1347 self._copied = (copied, nullid)
1347 self._copied = (copied, nullid)
1348
1348
1349 def __nonzero__(self):
1349 def __nonzero__(self):
1350 return True
1350 return True
1351 def __str__(self):
1351 def __str__(self):
1352 return "%s@%s" % (self.path(), self._changectx)
1352 return "%s@%s" % (self.path(), self._changectx)
1353 def path(self):
1353 def path(self):
1354 return self._path
1354 return self._path
1355 def data(self):
1355 def data(self):
1356 return self._data
1356 return self._data
1357 def flags(self):
1357 def flags(self):
1358 return self._flags
1358 return self._flags
1359 def isexec(self):
1359 def isexec(self):
1360 return 'x' in self._flags
1360 return 'x' in self._flags
1361 def islink(self):
1361 def islink(self):
1362 return 'l' in self._flags
1362 return 'l' in self._flags
1363 def renamed(self):
1363 def renamed(self):
1364 return self._copied
1364 return self._copied
@@ -1,90 +1,93 b''
1 # error.py - Mercurial exceptions
1 # error.py - Mercurial exceptions
2 #
2 #
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Mercurial exceptions.
8 """Mercurial exceptions.
9
9
10 This allows us to catch exceptions at higher levels without forcing
10 This allows us to catch exceptions at higher levels without forcing
11 imports.
11 imports.
12 """
12 """
13
13
14 # Do not import anything here, please
14 # Do not import anything here, please
15
15
16 class RevlogError(Exception):
16 class RevlogError(Exception):
17 pass
17 pass
18
18
19 class LookupError(RevlogError, KeyError):
19 class LookupError(RevlogError, KeyError):
20 def __init__(self, name, index, message):
20 def __init__(self, name, index, message):
21 self.name = name
21 self.name = name
22 if isinstance(name, str) and len(name) == 20:
22 if isinstance(name, str) and len(name) == 20:
23 from node import short
23 from node import short
24 name = short(name)
24 name = short(name)
25 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
25 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
26
26
27 def __str__(self):
27 def __str__(self):
28 return RevlogError.__str__(self)
28 return RevlogError.__str__(self)
29
29
30 class ManifestLookupError(LookupError):
31 pass
32
30 class CommandError(Exception):
33 class CommandError(Exception):
31 """Exception raised on errors in parsing the command line."""
34 """Exception raised on errors in parsing the command line."""
32
35
33 class Abort(Exception):
36 class Abort(Exception):
34 """Raised if a command needs to print an error and exit."""
37 """Raised if a command needs to print an error and exit."""
35 def __init__(self, *args, **kw):
38 def __init__(self, *args, **kw):
36 Exception.__init__(self, *args)
39 Exception.__init__(self, *args)
37 self.hint = kw.get('hint')
40 self.hint = kw.get('hint')
38
41
39 class ConfigError(Abort):
42 class ConfigError(Abort):
40 'Exception raised when parsing config files'
43 'Exception raised when parsing config files'
41
44
42 class OutOfBandError(Exception):
45 class OutOfBandError(Exception):
43 'Exception raised when a remote repo reports failure'
46 'Exception raised when a remote repo reports failure'
44
47
45 class ParseError(Exception):
48 class ParseError(Exception):
46 'Exception raised when parsing config files (msg[, pos])'
49 'Exception raised when parsing config files (msg[, pos])'
47
50
48 class RepoError(Exception):
51 class RepoError(Exception):
49 def __init__(self, *args, **kw):
52 def __init__(self, *args, **kw):
50 Exception.__init__(self, *args)
53 Exception.__init__(self, *args)
51 self.hint = kw.get('hint')
54 self.hint = kw.get('hint')
52
55
53 class RepoLookupError(RepoError):
56 class RepoLookupError(RepoError):
54 pass
57 pass
55
58
56 class CapabilityError(RepoError):
59 class CapabilityError(RepoError):
57 pass
60 pass
58
61
59 class RequirementError(RepoError):
62 class RequirementError(RepoError):
60 """Exception raised if .hg/requires has an unknown entry."""
63 """Exception raised if .hg/requires has an unknown entry."""
61 pass
64 pass
62
65
63 class LockError(IOError):
66 class LockError(IOError):
64 def __init__(self, errno, strerror, filename, desc):
67 def __init__(self, errno, strerror, filename, desc):
65 IOError.__init__(self, errno, strerror, filename)
68 IOError.__init__(self, errno, strerror, filename)
66 self.desc = desc
69 self.desc = desc
67
70
68 class LockHeld(LockError):
71 class LockHeld(LockError):
69 def __init__(self, errno, filename, desc, locker):
72 def __init__(self, errno, filename, desc, locker):
70 LockError.__init__(self, errno, 'Lock held', filename, desc)
73 LockError.__init__(self, errno, 'Lock held', filename, desc)
71 self.locker = locker
74 self.locker = locker
72
75
73 class LockUnavailable(LockError):
76 class LockUnavailable(LockError):
74 pass
77 pass
75
78
76 class ResponseError(Exception):
79 class ResponseError(Exception):
77 """Raised to print an error with part of output and exit."""
80 """Raised to print an error with part of output and exit."""
78
81
79 class UnknownCommand(Exception):
82 class UnknownCommand(Exception):
80 """Exception raised if command is not in the command table."""
83 """Exception raised if command is not in the command table."""
81
84
82 class AmbiguousCommand(Exception):
85 class AmbiguousCommand(Exception):
83 """Exception raised if command shortcut matches more than one command."""
86 """Exception raised if command shortcut matches more than one command."""
84
87
85 # derived from KeyboardInterrupt to simplify some breakout code
88 # derived from KeyboardInterrupt to simplify some breakout code
86 class SignalInterrupt(KeyboardInterrupt):
89 class SignalInterrupt(KeyboardInterrupt):
87 """Exception raised on SIGTERM and SIGHUP."""
90 """Exception raised on SIGTERM and SIGHUP."""
88
91
89 class SignatureError(Exception):
92 class SignatureError(Exception):
90 pass
93 pass
@@ -1,345 +1,346 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os
9 import os
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 from common import get_stat, ErrorResponse, permhooks, caching
11 from common import get_stat, ErrorResponse, permhooks, caching
12 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
12 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
13 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from request import wsgirequest
14 from request import wsgirequest
15 import webcommands, protocol, webutil
15 import webcommands, protocol, webutil
16
16
17 perms = {
17 perms = {
18 'changegroup': 'pull',
18 'changegroup': 'pull',
19 'changegroupsubset': 'pull',
19 'changegroupsubset': 'pull',
20 'getbundle': 'pull',
20 'getbundle': 'pull',
21 'stream_out': 'pull',
21 'stream_out': 'pull',
22 'listkeys': 'pull',
22 'listkeys': 'pull',
23 'unbundle': 'push',
23 'unbundle': 'push',
24 'pushkey': 'push',
24 'pushkey': 'push',
25 }
25 }
26
26
27 def makebreadcrumb(url, prefix=''):
27 def makebreadcrumb(url, prefix=''):
28 '''Return a 'URL breadcrumb' list
28 '''Return a 'URL breadcrumb' list
29
29
30 A 'URL breadcrumb' is a list of URL-name pairs,
30 A 'URL breadcrumb' is a list of URL-name pairs,
31 corresponding to each of the path items on a URL.
31 corresponding to each of the path items on a URL.
32 This can be used to create path navigation entries.
32 This can be used to create path navigation entries.
33 '''
33 '''
34 if url.endswith('/'):
34 if url.endswith('/'):
35 url = url[:-1]
35 url = url[:-1]
36 if prefix:
36 if prefix:
37 url = '/' + prefix + url
37 url = '/' + prefix + url
38 relpath = url
38 relpath = url
39 if relpath.startswith('/'):
39 if relpath.startswith('/'):
40 relpath = relpath[1:]
40 relpath = relpath[1:]
41
41
42 breadcrumb = []
42 breadcrumb = []
43 urlel = url
43 urlel = url
44 pathitems = [''] + relpath.split('/')
44 pathitems = [''] + relpath.split('/')
45 for pathel in reversed(pathitems):
45 for pathel in reversed(pathitems):
46 if not pathel or not urlel:
46 if not pathel or not urlel:
47 break
47 break
48 breadcrumb.append({'url': urlel, 'name': pathel})
48 breadcrumb.append({'url': urlel, 'name': pathel})
49 urlel = os.path.dirname(urlel)
49 urlel = os.path.dirname(urlel)
50 return reversed(breadcrumb)
50 return reversed(breadcrumb)
51
51
52
52
53 class hgweb(object):
53 class hgweb(object):
54 def __init__(self, repo, name=None, baseui=None):
54 def __init__(self, repo, name=None, baseui=None):
55 if isinstance(repo, str):
55 if isinstance(repo, str):
56 if baseui:
56 if baseui:
57 u = baseui.copy()
57 u = baseui.copy()
58 else:
58 else:
59 u = ui.ui()
59 u = ui.ui()
60 self.repo = hg.repository(u, repo)
60 self.repo = hg.repository(u, repo)
61 else:
61 else:
62 self.repo = repo
62 self.repo = repo
63
63
64 self.repo = self._getview(self.repo)
64 self.repo = self._getview(self.repo)
65 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
65 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
66 self.repo.ui.setconfig('ui', 'nontty', 'true')
66 self.repo.ui.setconfig('ui', 'nontty', 'true')
67 hook.redirect(True)
67 hook.redirect(True)
68 self.mtime = -1
68 self.mtime = -1
69 self.size = -1
69 self.size = -1
70 self.reponame = name
70 self.reponame = name
71 self.archives = 'zip', 'gz', 'bz2'
71 self.archives = 'zip', 'gz', 'bz2'
72 self.stripecount = 1
72 self.stripecount = 1
73 # a repo owner may set web.templates in .hg/hgrc to get any file
73 # a repo owner may set web.templates in .hg/hgrc to get any file
74 # readable by the user running the CGI script
74 # readable by the user running the CGI script
75 self.templatepath = self.config('web', 'templates')
75 self.templatepath = self.config('web', 'templates')
76
76
77 # The CGI scripts are often run by a user different from the repo owner.
77 # The CGI scripts are often run by a user different from the repo owner.
78 # Trust the settings from the .hg/hgrc files by default.
78 # Trust the settings from the .hg/hgrc files by default.
79 def config(self, section, name, default=None, untrusted=True):
79 def config(self, section, name, default=None, untrusted=True):
80 return self.repo.ui.config(section, name, default,
80 return self.repo.ui.config(section, name, default,
81 untrusted=untrusted)
81 untrusted=untrusted)
82
82
83 def configbool(self, section, name, default=False, untrusted=True):
83 def configbool(self, section, name, default=False, untrusted=True):
84 return self.repo.ui.configbool(section, name, default,
84 return self.repo.ui.configbool(section, name, default,
85 untrusted=untrusted)
85 untrusted=untrusted)
86
86
87 def configlist(self, section, name, default=None, untrusted=True):
87 def configlist(self, section, name, default=None, untrusted=True):
88 return self.repo.ui.configlist(section, name, default,
88 return self.repo.ui.configlist(section, name, default,
89 untrusted=untrusted)
89 untrusted=untrusted)
90
90
91 def _getview(self, repo):
91 def _getview(self, repo):
92 viewconfig = self.config('web', 'view', 'served')
92 viewconfig = self.config('web', 'view', 'served')
93 if viewconfig == 'all':
93 if viewconfig == 'all':
94 return repo.unfiltered()
94 return repo.unfiltered()
95 elif viewconfig in repoview.filtertable:
95 elif viewconfig in repoview.filtertable:
96 return repo.filtered(viewconfig)
96 return repo.filtered(viewconfig)
97 else:
97 else:
98 return repo.filtered('served')
98 return repo.filtered('served')
99
99
100 def refresh(self, request=None):
100 def refresh(self, request=None):
101 if request:
101 if request:
102 self.repo.ui.environ = request.env
102 self.repo.ui.environ = request.env
103 st = get_stat(self.repo.spath)
103 st = get_stat(self.repo.spath)
104 # compare changelog size in addition to mtime to catch
104 # compare changelog size in addition to mtime to catch
105 # rollbacks made less than a second ago
105 # rollbacks made less than a second ago
106 if st.st_mtime != self.mtime or st.st_size != self.size:
106 if st.st_mtime != self.mtime or st.st_size != self.size:
107 self.mtime = st.st_mtime
107 self.mtime = st.st_mtime
108 self.size = st.st_size
108 self.size = st.st_size
109 self.repo = hg.repository(self.repo.ui, self.repo.root)
109 self.repo = hg.repository(self.repo.ui, self.repo.root)
110 self.repo = self._getview(self.repo)
110 self.repo = self._getview(self.repo)
111 self.maxchanges = int(self.config("web", "maxchanges", 10))
111 self.maxchanges = int(self.config("web", "maxchanges", 10))
112 self.stripecount = int(self.config("web", "stripes", 1))
112 self.stripecount = int(self.config("web", "stripes", 1))
113 self.maxshortchanges = int(self.config("web", "maxshortchanges",
113 self.maxshortchanges = int(self.config("web", "maxshortchanges",
114 60))
114 60))
115 self.maxfiles = int(self.config("web", "maxfiles", 10))
115 self.maxfiles = int(self.config("web", "maxfiles", 10))
116 self.allowpull = self.configbool("web", "allowpull", True)
116 self.allowpull = self.configbool("web", "allowpull", True)
117 encoding.encoding = self.config("web", "encoding",
117 encoding.encoding = self.config("web", "encoding",
118 encoding.encoding)
118 encoding.encoding)
119
119
120 def run(self):
120 def run(self):
121 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
121 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
122 raise RuntimeError("This function is only intended to be "
122 raise RuntimeError("This function is only intended to be "
123 "called while running as a CGI script.")
123 "called while running as a CGI script.")
124 import mercurial.hgweb.wsgicgi as wsgicgi
124 import mercurial.hgweb.wsgicgi as wsgicgi
125 wsgicgi.launch(self)
125 wsgicgi.launch(self)
126
126
127 def __call__(self, env, respond):
127 def __call__(self, env, respond):
128 req = wsgirequest(env, respond)
128 req = wsgirequest(env, respond)
129 return self.run_wsgi(req)
129 return self.run_wsgi(req)
130
130
131 def run_wsgi(self, req):
131 def run_wsgi(self, req):
132
132
133 self.refresh(req)
133 self.refresh(req)
134
134
135 # work with CGI variables to create coherent structure
135 # work with CGI variables to create coherent structure
136 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
136 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
137
137
138 req.url = req.env['SCRIPT_NAME']
138 req.url = req.env['SCRIPT_NAME']
139 if not req.url.endswith('/'):
139 if not req.url.endswith('/'):
140 req.url += '/'
140 req.url += '/'
141 if 'REPO_NAME' in req.env:
141 if 'REPO_NAME' in req.env:
142 req.url += req.env['REPO_NAME'] + '/'
142 req.url += req.env['REPO_NAME'] + '/'
143
143
144 if 'PATH_INFO' in req.env:
144 if 'PATH_INFO' in req.env:
145 parts = req.env['PATH_INFO'].strip('/').split('/')
145 parts = req.env['PATH_INFO'].strip('/').split('/')
146 repo_parts = req.env.get('REPO_NAME', '').split('/')
146 repo_parts = req.env.get('REPO_NAME', '').split('/')
147 if parts[:len(repo_parts)] == repo_parts:
147 if parts[:len(repo_parts)] == repo_parts:
148 parts = parts[len(repo_parts):]
148 parts = parts[len(repo_parts):]
149 query = '/'.join(parts)
149 query = '/'.join(parts)
150 else:
150 else:
151 query = req.env['QUERY_STRING'].split('&', 1)[0]
151 query = req.env['QUERY_STRING'].split('&', 1)[0]
152 query = query.split(';', 1)[0]
152 query = query.split(';', 1)[0]
153
153
154 # process this if it's a protocol request
154 # process this if it's a protocol request
155 # protocol bits don't need to create any URLs
155 # protocol bits don't need to create any URLs
156 # and the clients always use the old URL structure
156 # and the clients always use the old URL structure
157
157
158 cmd = req.form.get('cmd', [''])[0]
158 cmd = req.form.get('cmd', [''])[0]
159 if protocol.iscmd(cmd):
159 if protocol.iscmd(cmd):
160 try:
160 try:
161 if query:
161 if query:
162 raise ErrorResponse(HTTP_NOT_FOUND)
162 raise ErrorResponse(HTTP_NOT_FOUND)
163 if cmd in perms:
163 if cmd in perms:
164 self.check_perm(req, perms[cmd])
164 self.check_perm(req, perms[cmd])
165 return protocol.call(self.repo, req, cmd)
165 return protocol.call(self.repo, req, cmd)
166 except ErrorResponse, inst:
166 except ErrorResponse, inst:
167 # A client that sends unbundle without 100-continue will
167 # A client that sends unbundle without 100-continue will
168 # break if we respond early.
168 # break if we respond early.
169 if (cmd == 'unbundle' and
169 if (cmd == 'unbundle' and
170 (req.env.get('HTTP_EXPECT',
170 (req.env.get('HTTP_EXPECT',
171 '').lower() != '100-continue') or
171 '').lower() != '100-continue') or
172 req.env.get('X-HgHttp2', '')):
172 req.env.get('X-HgHttp2', '')):
173 req.drain()
173 req.drain()
174 req.respond(inst, protocol.HGTYPE,
174 req.respond(inst, protocol.HGTYPE,
175 body='0\n%s\n' % inst.message)
175 body='0\n%s\n' % inst.message)
176 return ''
176 return ''
177
177
178 # translate user-visible url structure to internal structure
178 # translate user-visible url structure to internal structure
179
179
180 args = query.split('/', 2)
180 args = query.split('/', 2)
181 if 'cmd' not in req.form and args and args[0]:
181 if 'cmd' not in req.form and args and args[0]:
182
182
183 cmd = args.pop(0)
183 cmd = args.pop(0)
184 style = cmd.rfind('-')
184 style = cmd.rfind('-')
185 if style != -1:
185 if style != -1:
186 req.form['style'] = [cmd[:style]]
186 req.form['style'] = [cmd[:style]]
187 cmd = cmd[style + 1:]
187 cmd = cmd[style + 1:]
188
188
189 # avoid accepting e.g. style parameter as command
189 # avoid accepting e.g. style parameter as command
190 if util.safehasattr(webcommands, cmd):
190 if util.safehasattr(webcommands, cmd):
191 req.form['cmd'] = [cmd]
191 req.form['cmd'] = [cmd]
192 else:
192 else:
193 cmd = ''
193 cmd = ''
194
194
195 if cmd == 'static':
195 if cmd == 'static':
196 req.form['file'] = ['/'.join(args)]
196 req.form['file'] = ['/'.join(args)]
197 else:
197 else:
198 if args and args[0]:
198 if args and args[0]:
199 node = args.pop(0)
199 node = args.pop(0)
200 req.form['node'] = [node]
200 req.form['node'] = [node]
201 if args:
201 if args:
202 req.form['file'] = args
202 req.form['file'] = args
203
203
204 ua = req.env.get('HTTP_USER_AGENT', '')
204 ua = req.env.get('HTTP_USER_AGENT', '')
205 if cmd == 'rev' and 'mercurial' in ua:
205 if cmd == 'rev' and 'mercurial' in ua:
206 req.form['style'] = ['raw']
206 req.form['style'] = ['raw']
207
207
208 if cmd == 'archive':
208 if cmd == 'archive':
209 fn = req.form['node'][0]
209 fn = req.form['node'][0]
210 for type_, spec in self.archive_specs.iteritems():
210 for type_, spec in self.archive_specs.iteritems():
211 ext = spec[2]
211 ext = spec[2]
212 if fn.endswith(ext):
212 if fn.endswith(ext):
213 req.form['node'] = [fn[:-len(ext)]]
213 req.form['node'] = [fn[:-len(ext)]]
214 req.form['type'] = [type_]
214 req.form['type'] = [type_]
215
215
216 # process the web interface request
216 # process the web interface request
217
217
218 try:
218 try:
219 tmpl = self.templater(req)
219 tmpl = self.templater(req)
220 ctype = tmpl('mimetype', encoding=encoding.encoding)
220 ctype = tmpl('mimetype', encoding=encoding.encoding)
221 ctype = templater.stringify(ctype)
221 ctype = templater.stringify(ctype)
222
222
223 # check read permissions non-static content
223 # check read permissions non-static content
224 if cmd != 'static':
224 if cmd != 'static':
225 self.check_perm(req, None)
225 self.check_perm(req, None)
226
226
227 if cmd == '':
227 if cmd == '':
228 req.form['cmd'] = [tmpl.cache['default']]
228 req.form['cmd'] = [tmpl.cache['default']]
229 cmd = req.form['cmd'][0]
229 cmd = req.form['cmd'][0]
230
230
231 if self.configbool('web', 'cache', True):
231 if self.configbool('web', 'cache', True):
232 caching(self, req) # sets ETag header or raises NOT_MODIFIED
232 caching(self, req) # sets ETag header or raises NOT_MODIFIED
233 if cmd not in webcommands.__all__:
233 if cmd not in webcommands.__all__:
234 msg = 'no such method: %s' % cmd
234 msg = 'no such method: %s' % cmd
235 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
235 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
236 elif cmd == 'file' and 'raw' in req.form.get('style', []):
236 elif cmd == 'file' and 'raw' in req.form.get('style', []):
237 self.ctype = ctype
237 self.ctype = ctype
238 content = webcommands.rawfile(self, req, tmpl)
238 content = webcommands.rawfile(self, req, tmpl)
239 else:
239 else:
240 content = getattr(webcommands, cmd)(self, req, tmpl)
240 content = getattr(webcommands, cmd)(self, req, tmpl)
241 req.respond(HTTP_OK, ctype)
241 req.respond(HTTP_OK, ctype)
242
242
243 return content
243 return content
244
244
245 except (error.LookupError, error.RepoLookupError), err:
245 except (error.LookupError, error.RepoLookupError), err:
246 req.respond(HTTP_NOT_FOUND, ctype)
246 req.respond(HTTP_NOT_FOUND, ctype)
247 msg = str(err)
247 msg = str(err)
248 if util.safehasattr(err, 'name') and 'manifest' not in msg:
248 if (util.safehasattr(err, 'name') and
249 not isinstance(err, error.ManifestLookupError)):
249 msg = 'revision not found: %s' % err.name
250 msg = 'revision not found: %s' % err.name
250 return tmpl('error', error=msg)
251 return tmpl('error', error=msg)
251 except (error.RepoError, error.RevlogError), inst:
252 except (error.RepoError, error.RevlogError), inst:
252 req.respond(HTTP_SERVER_ERROR, ctype)
253 req.respond(HTTP_SERVER_ERROR, ctype)
253 return tmpl('error', error=str(inst))
254 return tmpl('error', error=str(inst))
254 except ErrorResponse, inst:
255 except ErrorResponse, inst:
255 req.respond(inst, ctype)
256 req.respond(inst, ctype)
256 if inst.code == HTTP_NOT_MODIFIED:
257 if inst.code == HTTP_NOT_MODIFIED:
257 # Not allowed to return a body on a 304
258 # Not allowed to return a body on a 304
258 return ['']
259 return ['']
259 return tmpl('error', error=inst.message)
260 return tmpl('error', error=inst.message)
260
261
261 def templater(self, req):
262 def templater(self, req):
262
263
263 # determine scheme, port and server name
264 # determine scheme, port and server name
264 # this is needed to create absolute urls
265 # this is needed to create absolute urls
265
266
266 proto = req.env.get('wsgi.url_scheme')
267 proto = req.env.get('wsgi.url_scheme')
267 if proto == 'https':
268 if proto == 'https':
268 proto = 'https'
269 proto = 'https'
269 default_port = "443"
270 default_port = "443"
270 else:
271 else:
271 proto = 'http'
272 proto = 'http'
272 default_port = "80"
273 default_port = "80"
273
274
274 port = req.env["SERVER_PORT"]
275 port = req.env["SERVER_PORT"]
275 port = port != default_port and (":" + port) or ""
276 port = port != default_port and (":" + port) or ""
276 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
277 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
277 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
278 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
278 logoimg = self.config("web", "logoimg", "hglogo.png")
279 logoimg = self.config("web", "logoimg", "hglogo.png")
279 staticurl = self.config("web", "staticurl") or req.url + 'static/'
280 staticurl = self.config("web", "staticurl") or req.url + 'static/'
280 if not staticurl.endswith('/'):
281 if not staticurl.endswith('/'):
281 staticurl += '/'
282 staticurl += '/'
282
283
283 # some functions for the templater
284 # some functions for the templater
284
285
285 def header(**map):
286 def header(**map):
286 yield tmpl('header', encoding=encoding.encoding, **map)
287 yield tmpl('header', encoding=encoding.encoding, **map)
287
288
288 def footer(**map):
289 def footer(**map):
289 yield tmpl("footer", **map)
290 yield tmpl("footer", **map)
290
291
291 def motd(**map):
292 def motd(**map):
292 yield self.config("web", "motd", "")
293 yield self.config("web", "motd", "")
293
294
294 # figure out which style to use
295 # figure out which style to use
295
296
296 vars = {}
297 vars = {}
297 styles = (
298 styles = (
298 req.form.get('style', [None])[0],
299 req.form.get('style', [None])[0],
299 self.config('web', 'style'),
300 self.config('web', 'style'),
300 'paper',
301 'paper',
301 )
302 )
302 style, mapfile = templater.stylemap(styles, self.templatepath)
303 style, mapfile = templater.stylemap(styles, self.templatepath)
303 if style == styles[0]:
304 if style == styles[0]:
304 vars['style'] = style
305 vars['style'] = style
305
306
306 start = req.url[-1] == '?' and '&' or '?'
307 start = req.url[-1] == '?' and '&' or '?'
307 sessionvars = webutil.sessionvars(vars, start)
308 sessionvars = webutil.sessionvars(vars, start)
308
309
309 if not self.reponame:
310 if not self.reponame:
310 self.reponame = (self.config("web", "name")
311 self.reponame = (self.config("web", "name")
311 or req.env.get('REPO_NAME')
312 or req.env.get('REPO_NAME')
312 or req.url.strip('/') or self.repo.root)
313 or req.url.strip('/') or self.repo.root)
313
314
314 # create the templater
315 # create the templater
315
316
316 tmpl = templater.templater(mapfile,
317 tmpl = templater.templater(mapfile,
317 defaults={"url": req.url,
318 defaults={"url": req.url,
318 "logourl": logourl,
319 "logourl": logourl,
319 "logoimg": logoimg,
320 "logoimg": logoimg,
320 "staticurl": staticurl,
321 "staticurl": staticurl,
321 "urlbase": urlbase,
322 "urlbase": urlbase,
322 "repo": self.reponame,
323 "repo": self.reponame,
323 "header": header,
324 "header": header,
324 "footer": footer,
325 "footer": footer,
325 "motd": motd,
326 "motd": motd,
326 "sessionvars": sessionvars,
327 "sessionvars": sessionvars,
327 "pathdef": makebreadcrumb(req.url),
328 "pathdef": makebreadcrumb(req.url),
328 })
329 })
329 return tmpl
330 return tmpl
330
331
331 def archivelist(self, nodeid):
332 def archivelist(self, nodeid):
332 allowed = self.configlist("web", "allow_archive")
333 allowed = self.configlist("web", "allow_archive")
333 for i, spec in self.archive_specs.iteritems():
334 for i, spec in self.archive_specs.iteritems():
334 if i in allowed or self.configbool("web", "allow" + i):
335 if i in allowed or self.configbool("web", "allow" + i):
335 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
336 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
336
337
337 archive_specs = {
338 archive_specs = {
338 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
339 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
339 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
340 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
340 'zip': ('application/zip', 'zip', '.zip', None),
341 'zip': ('application/zip', 'zip', '.zip', None),
341 }
342 }
342
343
343 def check_perm(self, req, op):
344 def check_perm(self, req, op):
344 for hook in permhooks:
345 for hook in permhooks:
345 hook(self, req, op)
346 hook(self, req, op)
General Comments 0
You need to be logged in to leave comments. Login now