##// END OF EJS Templates
statichttprepo: don't modify localrepo class variables...
Mads Kiilerich -
r13440:286a3720 stable
parent child Browse files
Show More
@@ -1,145 +1,145
1 # statichttprepo.py - simple http repository class for mercurial
1 # statichttprepo.py - simple http repository class for mercurial
2 #
2 #
3 # This provides read-only repo access to repositories exported via static http
3 # This provides read-only repo access to repositories exported via static http
4 #
4 #
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
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.
8 # GNU General Public License version 2 or any later version.
9
9
10 from i18n import _
10 from i18n import _
11 import changelog, byterange, url, error
11 import changelog, byterange, url, error
12 import localrepo, manifest, util, store
12 import localrepo, manifest, util, store
13 import urllib, urllib2, errno
13 import urllib, urllib2, errno
14
14
15 class httprangereader(object):
15 class httprangereader(object):
16 def __init__(self, url, opener):
16 def __init__(self, url, opener):
17 # we assume opener has HTTPRangeHandler
17 # we assume opener has HTTPRangeHandler
18 self.url = url
18 self.url = url
19 self.pos = 0
19 self.pos = 0
20 self.opener = opener
20 self.opener = opener
21 self.name = url
21 self.name = url
22 def seek(self, pos):
22 def seek(self, pos):
23 self.pos = pos
23 self.pos = pos
24 def read(self, bytes=None):
24 def read(self, bytes=None):
25 req = urllib2.Request(self.url)
25 req = urllib2.Request(self.url)
26 end = ''
26 end = ''
27 if bytes:
27 if bytes:
28 end = self.pos + bytes - 1
28 end = self.pos + bytes - 1
29 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
29 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
30
30
31 try:
31 try:
32 f = self.opener.open(req)
32 f = self.opener.open(req)
33 data = f.read()
33 data = f.read()
34 if hasattr(f, 'getcode'):
34 if hasattr(f, 'getcode'):
35 # python 2.6+
35 # python 2.6+
36 code = f.getcode()
36 code = f.getcode()
37 elif hasattr(f, 'code'):
37 elif hasattr(f, 'code'):
38 # undocumented attribute, seems to be set in 2.4 and 2.5
38 # undocumented attribute, seems to be set in 2.4 and 2.5
39 code = f.code
39 code = f.code
40 else:
40 else:
41 # Don't know how to check, hope for the best.
41 # Don't know how to check, hope for the best.
42 code = 206
42 code = 206
43 except urllib2.HTTPError, inst:
43 except urllib2.HTTPError, inst:
44 num = inst.code == 404 and errno.ENOENT or None
44 num = inst.code == 404 and errno.ENOENT or None
45 raise IOError(num, inst)
45 raise IOError(num, inst)
46 except urllib2.URLError, inst:
46 except urllib2.URLError, inst:
47 raise IOError(None, inst.reason[1])
47 raise IOError(None, inst.reason[1])
48
48
49 if code == 200:
49 if code == 200:
50 # HTTPRangeHandler does nothing if remote does not support
50 # HTTPRangeHandler does nothing if remote does not support
51 # Range headers and returns the full entity. Let's slice it.
51 # Range headers and returns the full entity. Let's slice it.
52 if bytes:
52 if bytes:
53 data = data[self.pos:self.pos + bytes]
53 data = data[self.pos:self.pos + bytes]
54 else:
54 else:
55 data = data[self.pos:]
55 data = data[self.pos:]
56 elif bytes:
56 elif bytes:
57 data = data[:bytes]
57 data = data[:bytes]
58 self.pos += len(data)
58 self.pos += len(data)
59 return data
59 return data
60 def __iter__(self):
60 def __iter__(self):
61 return iter(self.read().splitlines(1))
61 return iter(self.read().splitlines(1))
62 def close(self):
62 def close(self):
63 pass
63 pass
64
64
65 def build_opener(ui, authinfo):
65 def build_opener(ui, authinfo):
66 # urllib cannot handle URLs with embedded user or passwd
66 # urllib cannot handle URLs with embedded user or passwd
67 urlopener = url.opener(ui, authinfo)
67 urlopener = url.opener(ui, authinfo)
68 urlopener.add_handler(byterange.HTTPRangeHandler())
68 urlopener.add_handler(byterange.HTTPRangeHandler())
69
69
70 def opener(base):
70 def opener(base):
71 """return a function that opens files over http"""
71 """return a function that opens files over http"""
72 p = base
72 p = base
73 def o(path, mode="r", atomictemp=None):
73 def o(path, mode="r", atomictemp=None):
74 if 'a' in mode or 'w' in mode:
74 if 'a' in mode or 'w' in mode:
75 raise IOError('Permission denied')
75 raise IOError('Permission denied')
76 f = "/".join((p, urllib.quote(path)))
76 f = "/".join((p, urllib.quote(path)))
77 return httprangereader(f, urlopener)
77 return httprangereader(f, urlopener)
78 return o
78 return o
79
79
80 return opener
80 return opener
81
81
82 class statichttprepository(localrepo.localrepository):
82 class statichttprepository(localrepo.localrepository):
83 def __init__(self, ui, path):
83 def __init__(self, ui, path):
84 self._url = path
84 self._url = path
85 self.ui = ui
85 self.ui = ui
86
86
87 self.root = path
87 self.root = path
88 self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg")
88 self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg")
89
89
90 opener = build_opener(ui, authinfo)
90 opener = build_opener(ui, authinfo)
91 self.opener = opener(self.path)
91 self.opener = opener(self.path)
92
92
93 # find requirements
93 # find requirements
94 try:
94 try:
95 requirements = self.opener("requires").read().splitlines()
95 requirements = self.opener("requires").read().splitlines()
96 except IOError, inst:
96 except IOError, inst:
97 if inst.errno != errno.ENOENT:
97 if inst.errno != errno.ENOENT:
98 raise
98 raise
99 # check if it is a non-empty old-style repository
99 # check if it is a non-empty old-style repository
100 try:
100 try:
101 fp = self.opener("00changelog.i")
101 fp = self.opener("00changelog.i")
102 fp.read(1)
102 fp.read(1)
103 fp.close()
103 fp.close()
104 except IOError, inst:
104 except IOError, inst:
105 if inst.errno != errno.ENOENT:
105 if inst.errno != errno.ENOENT:
106 raise
106 raise
107 # we do not care about empty old-style repositories here
107 # we do not care about empty old-style repositories here
108 msg = _("'%s' does not appear to be an hg repository") % path
108 msg = _("'%s' does not appear to be an hg repository") % path
109 raise error.RepoError(msg)
109 raise error.RepoError(msg)
110 requirements = []
110 requirements = []
111
111
112 # check them
112 # check them
113 for r in requirements:
113 for r in requirements:
114 if r not in self.supported:
114 if r not in self.supported:
115 raise error.RepoError(_("requirement '%s' not supported") % r)
115 raise error.RepoError(_("requirement '%s' not supported") % r)
116
116
117 # setup store
117 # setup store
118 self.store = store.store(requirements, self.path, opener)
118 self.store = store.store(requirements, self.path, opener)
119 self.spath = self.store.path
119 self.spath = self.store.path
120 self.sopener = self.store.opener
120 self.sopener = self.store.opener
121 self.sjoin = self.store.join
121 self.sjoin = self.store.join
122
122
123 self.manifest = manifest.manifest(self.sopener)
123 self.manifest = manifest.manifest(self.sopener)
124 self.changelog = changelog.changelog(self.sopener)
124 self.changelog = changelog.changelog(self.sopener)
125 self._tags = None
125 self._tags = None
126 self.nodetagscache = None
126 self.nodetagscache = None
127 self._branchcache = None
127 self._branchcache = None
128 self._branchcachetip = None
128 self._branchcachetip = None
129 self.encodepats = None
129 self.encodepats = None
130 self.decodepats = None
130 self.decodepats = None
131 self.capabilities.remove("pushkey")
131 self.capabilities = self.capabilities.difference(["pushkey"])
132
132
133 def url(self):
133 def url(self):
134 return self._url
134 return self._url
135
135
136 def local(self):
136 def local(self):
137 return False
137 return False
138
138
139 def lock(self, wait=True):
139 def lock(self, wait=True):
140 raise util.Abort(_('cannot lock static-http repository'))
140 raise util.Abort(_('cannot lock static-http repository'))
141
141
142 def instance(ui, path, create):
142 def instance(ui, path, create):
143 if create:
143 if create:
144 raise util.Abort(_('cannot create new static-http repository'))
144 raise util.Abort(_('cannot create new static-http repository'))
145 return statichttprepository(ui, path[7:])
145 return statichttprepository(ui, path[7:])
@@ -1,156 +1,165
1
1
2 $ hg clone http://localhost:$HGPORT/ copy
2 $ hg clone http://localhost:$HGPORT/ copy
3 abort: error: Connection refused
3 abort: error: Connection refused
4 [255]
4 [255]
5 $ test -d copy
5 $ test -d copy
6 [1]
6 [1]
7
7
8 This server doesn't do range requests so it's basically only good for
8 This server doesn't do range requests so it's basically only good for
9 one pull
9 one pull
10
10
11 $ cat > dumb.py <<EOF
11 $ cat > dumb.py <<EOF
12 > import BaseHTTPServer, SimpleHTTPServer, os, signal, sys
12 > import BaseHTTPServer, SimpleHTTPServer, os, signal, sys
13 >
13 >
14 > def run(server_class=BaseHTTPServer.HTTPServer,
14 > def run(server_class=BaseHTTPServer.HTTPServer,
15 > handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler):
15 > handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler):
16 > server_address = ('localhost', int(os.environ['HGPORT']))
16 > server_address = ('localhost', int(os.environ['HGPORT']))
17 > httpd = server_class(server_address, handler_class)
17 > httpd = server_class(server_address, handler_class)
18 > httpd.serve_forever()
18 > httpd.serve_forever()
19 >
19 >
20 > signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
20 > signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
21 > run()
21 > run()
22 > EOF
22 > EOF
23 $ python dumb.py 2>/dev/null &
23 $ python dumb.py 2>/dev/null &
24 $ echo $! >> $DAEMON_PIDS
24 $ echo $! >> $DAEMON_PIDS
25 $ mkdir remote
25 $ mkdir remote
26 $ cd remote
26 $ cd remote
27 $ hg init
27 $ hg init
28 $ echo foo > bar
28 $ echo foo > bar
29 $ echo c2 > '.dotfile with spaces'
29 $ echo c2 > '.dotfile with spaces'
30 $ hg add
30 $ hg add
31 adding .dotfile with spaces
31 adding .dotfile with spaces
32 adding bar
32 adding bar
33 $ hg commit -m"test"
33 $ hg commit -m"test"
34 $ hg tip
34 $ hg tip
35 changeset: 0:02770d679fb8
35 changeset: 0:02770d679fb8
36 tag: tip
36 tag: tip
37 user: test
37 user: test
38 date: Thu Jan 01 00:00:00 1970 +0000
38 date: Thu Jan 01 00:00:00 1970 +0000
39 summary: test
39 summary: test
40
40
41 $ cd ..
41 $ cd ..
42 $ hg clone static-http://localhost:$HGPORT/remote local
42 $ hg clone static-http://localhost:$HGPORT/remote local
43 requesting all changes
43 requesting all changes
44 adding changesets
44 adding changesets
45 adding manifests
45 adding manifests
46 adding file changes
46 adding file changes
47 added 1 changesets with 2 changes to 2 files
47 added 1 changesets with 2 changes to 2 files
48 updating to branch default
48 updating to branch default
49 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 $ cd local
50 $ cd local
51 $ hg verify
51 $ hg verify
52 checking changesets
52 checking changesets
53 checking manifests
53 checking manifests
54 crosschecking files in changesets and manifests
54 crosschecking files in changesets and manifests
55 checking files
55 checking files
56 2 files, 1 changesets, 2 total revisions
56 2 files, 1 changesets, 2 total revisions
57 $ cat bar
57 $ cat bar
58 foo
58 foo
59 $ cd ../remote
59 $ cd ../remote
60 $ echo baz > quux
60 $ echo baz > quux
61 $ hg commit -A -mtest2
61 $ hg commit -A -mtest2
62 adding quux
62 adding quux
63
63
64 check for HTTP opener failures when cachefile does not exist
64 check for HTTP opener failures when cachefile does not exist
65
65
66 $ rm .hg/cache/*
66 $ rm .hg/cache/*
67 $ cd ../local
67 $ cd ../local
68 $ echo '[hooks]' >> .hg/hgrc
68 $ echo '[hooks]' >> .hg/hgrc
69 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
69 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
70 $ hg pull
70 $ hg pull
71 pulling from static-http://localhost:$HGPORT/remote
71 pulling from static-http://localhost:$HGPORT/remote
72 searching for changes
72 searching for changes
73 adding changesets
73 adding changesets
74 adding manifests
74 adding manifests
75 adding file changes
75 adding file changes
76 added 1 changesets with 1 changes to 1 files
76 added 1 changesets with 1 changes to 1 files
77 changegroup hook: HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/remote
77 changegroup hook: HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/remote
78 (run 'hg update' to get a working copy)
78 (run 'hg update' to get a working copy)
79
79
80 trying to push
80 trying to push
81
81
82 $ hg update
82 $ hg update
83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 $ echo more foo >> bar
84 $ echo more foo >> bar
85 $ hg commit -m"test"
85 $ hg commit -m"test"
86 $ hg push
86 $ hg push
87 pushing to static-http://localhost:$HGPORT/remote
87 pushing to static-http://localhost:$HGPORT/remote
88 abort: cannot lock static-http repository
88 abort: cannot lock static-http repository
89 [255]
89 [255]
90
90
91 trying clone -r
91 trying clone -r
92
92
93 $ cd ..
93 $ cd ..
94 $ hg clone -r donotexist static-http://localhost:$HGPORT/remote local0
94 $ hg clone -r donotexist static-http://localhost:$HGPORT/remote local0
95 abort: unknown revision 'donotexist'!
95 abort: unknown revision 'donotexist'!
96 [255]
96 [255]
97 $ hg clone -r 0 static-http://localhost:$HGPORT/remote local0
97 $ hg clone -r 0 static-http://localhost:$HGPORT/remote local0
98 adding changesets
98 adding changesets
99 adding manifests
99 adding manifests
100 adding file changes
100 adding file changes
101 added 1 changesets with 2 changes to 2 files
101 added 1 changesets with 2 changes to 2 files
102 updating to branch default
102 updating to branch default
103 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
104
104
105 test with "/" URI (issue 747)
105 test with "/" URI (issue 747) and subrepo
106
106
107 $ hg init
107 $ hg init
108 $ hg init sub
109 $ hg -R sub tag not-empty
110 $ echo sub=sub > .hgsub
108 $ echo a > a
111 $ echo a > a
109 $ hg add a
112 $ hg add a .hgsub
110 $ hg ci -ma
113 $ hg -q ci -ma
111 $ hg clone static-http://localhost:$HGPORT/ local2
114 $ hg clone static-http://localhost:$HGPORT/ local2
112 requesting all changes
115 requesting all changes
113 adding changesets
116 adding changesets
114 adding manifests
117 adding manifests
115 adding file changes
118 adding file changes
116 added 1 changesets with 1 changes to 1 files
119 added 1 changesets with 3 changes to 3 files
117 updating to branch default
120 updating to branch default
118 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 pulling subrepo sub from static-http://localhost:$HGPORT/sub
122 requesting all changes
123 adding changesets
124 adding manifests
125 adding file changes
126 added 1 changesets with 1 changes to 1 files
127 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 $ cd local2
128 $ cd local2
120 $ hg verify
129 $ hg verify
121 checking changesets
130 checking changesets
122 checking manifests
131 checking manifests
123 crosschecking files in changesets and manifests
132 crosschecking files in changesets and manifests
124 checking files
133 checking files
125 1 files, 1 changesets, 1 total revisions
134 3 files, 1 changesets, 3 total revisions
126 $ cat a
135 $ cat a
127 a
136 a
128 $ hg paths
137 $ hg paths
129 default = static-http://localhost:$HGPORT/
138 default = static-http://localhost:$HGPORT/
130
139
131 test with empty repo (issue965)
140 test with empty repo (issue965)
132
141
133 $ cd ..
142 $ cd ..
134 $ hg init remotempty
143 $ hg init remotempty
135 $ hg clone static-http://localhost:$HGPORT/remotempty local3
144 $ hg clone static-http://localhost:$HGPORT/remotempty local3
136 no changes found
145 no changes found
137 updating to branch default
146 updating to branch default
138 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
147 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 $ cd local3
148 $ cd local3
140 $ hg verify
149 $ hg verify
141 checking changesets
150 checking changesets
142 checking manifests
151 checking manifests
143 crosschecking files in changesets and manifests
152 crosschecking files in changesets and manifests
144 checking files
153 checking files
145 0 files, 0 changesets, 0 total revisions
154 0 files, 0 changesets, 0 total revisions
146 $ hg paths
155 $ hg paths
147 default = static-http://localhost:$HGPORT/remotempty
156 default = static-http://localhost:$HGPORT/remotempty
148
157
149 test with non-repo
158 test with non-repo
150
159
151 $ cd ..
160 $ cd ..
152 $ mkdir notarepo
161 $ mkdir notarepo
153 $ hg clone static-http://localhost:$HGPORT/notarepo local3
162 $ hg clone static-http://localhost:$HGPORT/notarepo local3
154 abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository!
163 abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository!
155 [255]
164 [255]
156 $ kill $!
165 $ kill $!
General Comments 0
You need to be logged in to leave comments. Login now