|
1 # statichttprepo.py - simple http repository class for mercurial |
|
2 # |
|
3 # This provides read-only repo access to repositories exported via static http |
|
4 # |
|
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
6 # |
|
7 # This software may be used and distributed according to the terms of the |
|
8 # GNU General Public License version 2 or any later version. |
|
9 |
|
10 from i18n import _ |
|
11 import changelog, byterange, url, error |
|
12 import localrepo, manifest, util, store |
|
13 import urllib, urllib2, errno |
|
14 |
|
15 class httprangereader(object): |
|
16 def __init__(self, url, opener): |
|
17 # we assume opener has HTTPRangeHandler |
|
18 self.url = url |
|
19 self.pos = 0 |
|
20 self.opener = opener |
|
21 self.name = url |
|
22 def seek(self, pos): |
|
23 self.pos = pos |
|
24 def read(self, bytes=None): |
|
25 req = urllib2.Request(self.url) |
|
26 end = '' |
|
27 if bytes: |
|
28 end = self.pos + bytes - 1 |
|
29 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end)) |
|
30 |
|
31 try: |
|
32 f = self.opener.open(req) |
|
33 data = f.read() |
|
34 if hasattr(f, 'getcode'): |
|
35 # python 2.6+ |
|
36 code = f.getcode() |
|
37 elif hasattr(f, 'code'): |
|
38 # undocumented attribute, seems to be set in 2.4 and 2.5 |
|
39 code = f.code |
|
40 else: |
|
41 # Don't know how to check, hope for the best. |
|
42 code = 206 |
|
43 except urllib2.HTTPError, inst: |
|
44 num = inst.code == 404 and errno.ENOENT or None |
|
45 raise IOError(num, inst) |
|
46 except urllib2.URLError, inst: |
|
47 raise IOError(None, inst.reason[1]) |
|
48 |
|
49 if code == 200: |
|
50 # HTTPRangeHandler does nothing if remote does not support |
|
51 # Range headers and returns the full entity. Let's slice it. |
|
52 if bytes: |
|
53 data = data[self.pos:self.pos + bytes] |
|
54 else: |
|
55 data = data[self.pos:] |
|
56 elif bytes: |
|
57 data = data[:bytes] |
|
58 self.pos += len(data) |
|
59 return data |
|
60 def __iter__(self): |
|
61 return iter(self.read().splitlines(1)) |
|
62 def close(self): |
|
63 pass |
|
64 |
|
65 def build_opener(ui, authinfo): |
|
66 # urllib cannot handle URLs with embedded user or passwd |
|
67 urlopener = url.opener(ui, authinfo) |
|
68 urlopener.add_handler(byterange.HTTPRangeHandler()) |
|
69 |
|
70 def opener(base): |
|
71 """return a function that opens files over http""" |
|
72 p = base |
|
73 def o(path, mode="r", atomictemp=None): |
|
74 if 'a' in mode or 'w' in mode: |
|
75 raise IOError('Permission denied') |
|
76 f = "/".join((p, urllib.quote(path))) |
|
77 return httprangereader(f, urlopener) |
|
78 return o |
|
79 |
|
80 opener.options = {'nonlazy': 1} |
|
81 return opener |
|
82 |
|
83 class statichttprepository(localrepo.localrepository): |
|
84 def __init__(self, ui, path): |
|
85 self._url = path |
|
86 self.ui = ui |
|
87 |
|
88 self.root = path |
|
89 self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg") |
|
90 |
|
91 opener = build_opener(ui, authinfo) |
|
92 self.opener = opener(self.path) |
|
93 |
|
94 # find requirements |
|
95 try: |
|
96 requirements = self.opener("requires").read().splitlines() |
|
97 except IOError, inst: |
|
98 if inst.errno != errno.ENOENT: |
|
99 raise |
|
100 # check if it is a non-empty old-style repository |
|
101 try: |
|
102 self.opener("00changelog.i").read(1) |
|
103 except IOError, inst: |
|
104 if inst.errno != errno.ENOENT: |
|
105 raise |
|
106 # we do not care about empty old-style repositories here |
|
107 msg = _("'%s' does not appear to be an hg repository") % path |
|
108 raise error.RepoError(msg) |
|
109 requirements = [] |
|
110 |
|
111 # check them |
|
112 for r in requirements: |
|
113 if r not in self.supported: |
|
114 raise error.RepoError(_("requirement '%s' not supported") % r) |
|
115 |
|
116 # setup store |
|
117 def pjoin(a, b): |
|
118 return a + '/' + b |
|
119 self.store = store.store(requirements, self.path, opener, pjoin) |
|
120 self.spath = self.store.path |
|
121 self.sopener = self.store.opener |
|
122 self.sjoin = self.store.join |
|
123 |
|
124 self.manifest = manifest.manifest(self.sopener) |
|
125 self.changelog = changelog.changelog(self.sopener) |
|
126 self._tags = None |
|
127 self.nodetagscache = None |
|
128 self._branchcache = None |
|
129 self._branchcachetip = None |
|
130 self.encodepats = None |
|
131 self.decodepats = None |
|
132 self.capabilities.remove("pushkey") |
|
133 |
|
134 def url(self): |
|
135 return self._url |
|
136 |
|
137 def local(self): |
|
138 return False |
|
139 |
|
140 def lock(self, wait=True): |
|
141 raise util.Abort(_('cannot lock static-http repository')) |
|
142 |
|
143 def instance(ui, path, create): |
|
144 if create: |
|
145 raise util.Abort(_('cannot create new static-http repository')) |
|
146 return statichttprepository(ui, path[7:]) |