##// END OF EJS Templates
zeroconf: forward all arguments passed to ui.configitems() wrapper...
Yuya Nishihara -
r28038:72f2a19c stable
parent child Browse files
Show More
@@ -1,193 +1,193 b''
1 # zeroconf.py - zeroconf support for Mercurial
1 # zeroconf.py - zeroconf support for Mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''discover and advertise repositories on the local network
8 '''discover and advertise repositories on the local network
9
9
10 Zeroconf-enabled repositories will be announced in a network without
10 Zeroconf-enabled repositories will be announced in a network without
11 the need to configure a server or a service. They can be discovered
11 the need to configure a server or a service. They can be discovered
12 without knowing their actual IP address.
12 without knowing their actual IP address.
13
13
14 To allow other people to discover your repository using run
14 To allow other people to discover your repository using run
15 :hg:`serve` in your repository::
15 :hg:`serve` in your repository::
16
16
17 $ cd test
17 $ cd test
18 $ hg serve
18 $ hg serve
19
19
20 You can discover Zeroconf-enabled repositories by running
20 You can discover Zeroconf-enabled repositories by running
21 :hg:`paths`::
21 :hg:`paths`::
22
22
23 $ hg paths
23 $ hg paths
24 zc-test = http://example.com:8000/test
24 zc-test = http://example.com:8000/test
25 '''
25 '''
26
26
27 import socket, time, os
27 import socket, time, os
28
28
29 import Zeroconf
29 import Zeroconf
30 from mercurial import ui, hg, encoding, dispatch
30 from mercurial import ui, hg, encoding, dispatch
31 from mercurial import extensions
31 from mercurial import extensions
32 from mercurial.hgweb import server as servermod
32 from mercurial.hgweb import server as servermod
33
33
34 # Note for extension authors: ONLY specify testedwith = 'internal' for
34 # Note for extension authors: ONLY specify testedwith = 'internal' for
35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
36 # be specifying the version(s) of Mercurial they are tested with, or
36 # be specifying the version(s) of Mercurial they are tested with, or
37 # leave the attribute unspecified.
37 # leave the attribute unspecified.
38 testedwith = 'internal'
38 testedwith = 'internal'
39
39
40 # publish
40 # publish
41
41
42 server = None
42 server = None
43 localip = None
43 localip = None
44
44
45 def getip():
45 def getip():
46 # finds external-facing interface without sending any packets (Linux)
46 # finds external-facing interface without sending any packets (Linux)
47 try:
47 try:
48 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
48 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
49 s.connect(('1.0.0.1', 0))
49 s.connect(('1.0.0.1', 0))
50 ip = s.getsockname()[0]
50 ip = s.getsockname()[0]
51 return ip
51 return ip
52 except socket.error:
52 except socket.error:
53 pass
53 pass
54
54
55 # Generic method, sometimes gives useless results
55 # Generic method, sometimes gives useless results
56 try:
56 try:
57 dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
57 dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
58 if not dumbip.startswith('127.') and ':' not in dumbip:
58 if not dumbip.startswith('127.') and ':' not in dumbip:
59 return dumbip
59 return dumbip
60 except (socket.gaierror, socket.herror):
60 except (socket.gaierror, socket.herror):
61 dumbip = '127.0.0.1'
61 dumbip = '127.0.0.1'
62
62
63 # works elsewhere, but actually sends a packet
63 # works elsewhere, but actually sends a packet
64 try:
64 try:
65 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
65 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
66 s.connect(('1.0.0.1', 1))
66 s.connect(('1.0.0.1', 1))
67 ip = s.getsockname()[0]
67 ip = s.getsockname()[0]
68 return ip
68 return ip
69 except socket.error:
69 except socket.error:
70 pass
70 pass
71
71
72 return dumbip
72 return dumbip
73
73
74 def publish(name, desc, path, port):
74 def publish(name, desc, path, port):
75 global server, localip
75 global server, localip
76 if not server:
76 if not server:
77 ip = getip()
77 ip = getip()
78 if ip.startswith('127.'):
78 if ip.startswith('127.'):
79 # if we have no internet connection, this can happen.
79 # if we have no internet connection, this can happen.
80 return
80 return
81 localip = socket.inet_aton(ip)
81 localip = socket.inet_aton(ip)
82 server = Zeroconf.Zeroconf(ip)
82 server = Zeroconf.Zeroconf(ip)
83
83
84 hostname = socket.gethostname().split('.')[0]
84 hostname = socket.gethostname().split('.')[0]
85 host = hostname + ".local"
85 host = hostname + ".local"
86 name = "%s-%s" % (hostname, name)
86 name = "%s-%s" % (hostname, name)
87
87
88 # advertise to browsers
88 # advertise to browsers
89 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
89 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
90 name + '._http._tcp.local.',
90 name + '._http._tcp.local.',
91 server = host,
91 server = host,
92 port = port,
92 port = port,
93 properties = {'description': desc,
93 properties = {'description': desc,
94 'path': "/" + path},
94 'path': "/" + path},
95 address = localip, weight = 0, priority = 0)
95 address = localip, weight = 0, priority = 0)
96 server.registerService(svc)
96 server.registerService(svc)
97
97
98 # advertise to Mercurial clients
98 # advertise to Mercurial clients
99 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
99 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
100 name + '._hg._tcp.local.',
100 name + '._hg._tcp.local.',
101 server = host,
101 server = host,
102 port = port,
102 port = port,
103 properties = {'description': desc,
103 properties = {'description': desc,
104 'path': "/" + path},
104 'path': "/" + path},
105 address = localip, weight = 0, priority = 0)
105 address = localip, weight = 0, priority = 0)
106 server.registerService(svc)
106 server.registerService(svc)
107
107
108 def zc_create_server(create_server, ui, app):
108 def zc_create_server(create_server, ui, app):
109 httpd = create_server(ui, app)
109 httpd = create_server(ui, app)
110 port = httpd.port
110 port = httpd.port
111
111
112 try:
112 try:
113 repos = app.repos
113 repos = app.repos
114 except AttributeError:
114 except AttributeError:
115 # single repo
115 # single repo
116 with app._obtainrepo() as repo:
116 with app._obtainrepo() as repo:
117 name = app.reponame or os.path.basename(repo.root)
117 name = app.reponame or os.path.basename(repo.root)
118 path = repo.ui.config("web", "prefix", "").strip('/')
118 path = repo.ui.config("web", "prefix", "").strip('/')
119 desc = repo.ui.config("web", "description", name)
119 desc = repo.ui.config("web", "description", name)
120 publish(name, desc, path, port)
120 publish(name, desc, path, port)
121 else:
121 else:
122 # webdir
122 # webdir
123 prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
123 prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
124 for repo, path in repos:
124 for repo, path in repos:
125 u = app.ui.copy()
125 u = app.ui.copy()
126 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
126 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
127 name = os.path.basename(repo)
127 name = os.path.basename(repo)
128 path = (prefix + repo).strip('/')
128 path = (prefix + repo).strip('/')
129 desc = u.config('web', 'description', name)
129 desc = u.config('web', 'description', name)
130 publish(name, desc, path, port)
130 publish(name, desc, path, port)
131 return httpd
131 return httpd
132
132
133 # listen
133 # listen
134
134
135 class listener(object):
135 class listener(object):
136 def __init__(self):
136 def __init__(self):
137 self.found = {}
137 self.found = {}
138 def removeService(self, server, type, name):
138 def removeService(self, server, type, name):
139 if repr(name) in self.found:
139 if repr(name) in self.found:
140 del self.found[repr(name)]
140 del self.found[repr(name)]
141 def addService(self, server, type, name):
141 def addService(self, server, type, name):
142 self.found[repr(name)] = server.getServiceInfo(type, name)
142 self.found[repr(name)] = server.getServiceInfo(type, name)
143
143
144 def getzcpaths():
144 def getzcpaths():
145 ip = getip()
145 ip = getip()
146 if ip.startswith('127.'):
146 if ip.startswith('127.'):
147 return
147 return
148 server = Zeroconf.Zeroconf(ip)
148 server = Zeroconf.Zeroconf(ip)
149 l = listener()
149 l = listener()
150 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
150 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
151 time.sleep(1)
151 time.sleep(1)
152 server.close()
152 server.close()
153 for value in l.found.values():
153 for value in l.found.values():
154 name = value.name[:value.name.index('.')]
154 name = value.name[:value.name.index('.')]
155 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
155 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
156 value.properties.get("path", "/"))
156 value.properties.get("path", "/"))
157 yield "zc-" + name, url
157 yield "zc-" + name, url
158
158
159 def config(orig, self, section, key, default=None, untrusted=False):
159 def config(orig, self, section, key, default=None, untrusted=False):
160 if section == "paths" and key.startswith("zc-"):
160 if section == "paths" and key.startswith("zc-"):
161 for name, path in getzcpaths():
161 for name, path in getzcpaths():
162 if name == key:
162 if name == key:
163 return path
163 return path
164 return orig(self, section, key, default, untrusted)
164 return orig(self, section, key, default, untrusted)
165
165
166 def configitems(orig, self, section, untrusted=False):
166 def configitems(orig, self, section, *args, **kwargs):
167 repos = orig(self, section, untrusted)
167 repos = orig(self, section, *args, **kwargs)
168 if section == "paths":
168 if section == "paths":
169 repos += getzcpaths()
169 repos += getzcpaths()
170 return repos
170 return repos
171
171
172 def defaultdest(orig, source):
172 def defaultdest(orig, source):
173 for name, path in getzcpaths():
173 for name, path in getzcpaths():
174 if path == source:
174 if path == source:
175 return name.encode(encoding.encoding)
175 return name.encode(encoding.encoding)
176 return orig(source)
176 return orig(source)
177
177
178 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
178 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
179 try:
179 try:
180 return orig(ui, options, cmd, cmdfunc)
180 return orig(ui, options, cmd, cmdfunc)
181 finally:
181 finally:
182 # we need to call close() on the server to notify() the various
182 # we need to call close() on the server to notify() the various
183 # threading Conditions and allow the background threads to exit
183 # threading Conditions and allow the background threads to exit
184 global server
184 global server
185 if server:
185 if server:
186 server.close()
186 server.close()
187
187
188 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
188 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
189
189
190 extensions.wrapfunction(ui.ui, 'config', config)
190 extensions.wrapfunction(ui.ui, 'config', config)
191 extensions.wrapfunction(ui.ui, 'configitems', configitems)
191 extensions.wrapfunction(ui.ui, 'configitems', configitems)
192 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
192 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
193 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
193 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
@@ -1,173 +1,181 b''
1 $ hg init a
1 $ hg init a
2 $ hg clone a b
2 $ hg clone a b
3 updating to branch default
3 updating to branch default
4 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 $ cd a
5 $ cd a
6
6
7 with no paths:
7 with no paths:
8
8
9 $ hg paths
9 $ hg paths
10 $ hg paths unknown
10 $ hg paths unknown
11 not found!
11 not found!
12 [1]
12 [1]
13 $ hg paths -Tjson
13 $ hg paths -Tjson
14 [
14 [
15 ]
15 ]
16
16
17 with paths:
17 with paths:
18
18
19 $ echo '[paths]' >> .hg/hgrc
19 $ echo '[paths]' >> .hg/hgrc
20 $ echo 'dupe = ../b#tip' >> .hg/hgrc
20 $ echo 'dupe = ../b#tip' >> .hg/hgrc
21 $ echo 'expand = $SOMETHING/bar' >> .hg/hgrc
21 $ echo 'expand = $SOMETHING/bar' >> .hg/hgrc
22 $ hg in dupe
22 $ hg in dupe
23 comparing with $TESTTMP/b (glob)
23 comparing with $TESTTMP/b (glob)
24 no changes found
24 no changes found
25 [1]
25 [1]
26 $ cd ..
26 $ cd ..
27 $ hg -R a in dupe
27 $ hg -R a in dupe
28 comparing with $TESTTMP/b (glob)
28 comparing with $TESTTMP/b (glob)
29 no changes found
29 no changes found
30 [1]
30 [1]
31 $ cd a
31 $ cd a
32 $ hg paths
32 $ hg paths
33 dupe = $TESTTMP/b#tip (glob)
33 dupe = $TESTTMP/b#tip (glob)
34 expand = $TESTTMP/a/$SOMETHING/bar (glob)
34 expand = $TESTTMP/a/$SOMETHING/bar (glob)
35 $ SOMETHING=foo hg paths
35 $ SOMETHING=foo hg paths
36 dupe = $TESTTMP/b#tip (glob)
36 dupe = $TESTTMP/b#tip (glob)
37 expand = $TESTTMP/a/foo/bar (glob)
37 expand = $TESTTMP/a/foo/bar (glob)
38 #if msys
38 #if msys
39 $ SOMETHING=//foo hg paths
39 $ SOMETHING=//foo hg paths
40 dupe = $TESTTMP/b#tip (glob)
40 dupe = $TESTTMP/b#tip (glob)
41 expand = /foo/bar
41 expand = /foo/bar
42 #else
42 #else
43 $ SOMETHING=/foo hg paths
43 $ SOMETHING=/foo hg paths
44 dupe = $TESTTMP/b#tip (glob)
44 dupe = $TESTTMP/b#tip (glob)
45 expand = /foo/bar
45 expand = /foo/bar
46 #endif
46 #endif
47 $ hg paths -q
47 $ hg paths -q
48 dupe
48 dupe
49 expand
49 expand
50 $ hg paths dupe
50 $ hg paths dupe
51 $TESTTMP/b#tip (glob)
51 $TESTTMP/b#tip (glob)
52 $ hg paths -q dupe
52 $ hg paths -q dupe
53 $ hg paths unknown
53 $ hg paths unknown
54 not found!
54 not found!
55 [1]
55 [1]
56 $ hg paths -q unknown
56 $ hg paths -q unknown
57 [1]
57 [1]
58
58
59 formatter output with paths:
59 formatter output with paths:
60
60
61 $ echo 'dupe:pushurl = https://example.com/dupe' >> .hg/hgrc
61 $ echo 'dupe:pushurl = https://example.com/dupe' >> .hg/hgrc
62 $ hg paths -Tjson
62 $ hg paths -Tjson
63 [
63 [
64 {
64 {
65 "name": "dupe",
65 "name": "dupe",
66 "pushurl": "https://example.com/dupe",
66 "pushurl": "https://example.com/dupe",
67 "url": "$TESTTMP/b#tip"
67 "url": "$TESTTMP/b#tip"
68 },
68 },
69 {
69 {
70 "name": "expand",
70 "name": "expand",
71 "url": "$TESTTMP/a/$SOMETHING/bar"
71 "url": "$TESTTMP/a/$SOMETHING/bar"
72 }
72 }
73 ]
73 ]
74 $ hg paths -Tjson dupe
74 $ hg paths -Tjson dupe
75 [
75 [
76 {
76 {
77 "name": "dupe",
77 "name": "dupe",
78 "pushurl": "https://example.com/dupe",
78 "pushurl": "https://example.com/dupe",
79 "url": "$TESTTMP/b#tip"
79 "url": "$TESTTMP/b#tip"
80 }
80 }
81 ]
81 ]
82 $ hg paths -Tjson -q unknown
82 $ hg paths -Tjson -q unknown
83 [
83 [
84 ]
84 ]
85 [1]
85 [1]
86
86
87 password should be masked in plain output, but not in machine-readable output:
87 password should be masked in plain output, but not in machine-readable output:
88
88
89 $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc
89 $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc
90 $ hg paths insecure
90 $ hg paths insecure
91 http://foo:***@example.com/
91 http://foo:***@example.com/
92 $ hg paths -Tjson insecure
92 $ hg paths -Tjson insecure
93 [
93 [
94 {
94 {
95 "name": "insecure",
95 "name": "insecure",
96 "url": "http://foo:insecure@example.com/"
96 "url": "http://foo:insecure@example.com/"
97 }
97 }
98 ]
98 ]
99
99
100 zeroconf wraps ui.configitems(), which shouldn't crash at least:
101
102 $ hg paths --config extensions.zeroconf=
103 dupe = $TESTTMP/b#tip (glob)
104 dupe:pushurl = https://example.com/dupe
105 expand = $TESTTMP/a/$SOMETHING/bar (glob)
106 insecure = http://foo:***@example.com/
107
100 $ cd ..
108 $ cd ..
101
109
102 sub-options for an undeclared path are ignored
110 sub-options for an undeclared path are ignored
103
111
104 $ hg init suboptions
112 $ hg init suboptions
105 $ cd suboptions
113 $ cd suboptions
106
114
107 $ cat > .hg/hgrc << EOF
115 $ cat > .hg/hgrc << EOF
108 > [paths]
116 > [paths]
109 > path0 = https://example.com/path0
117 > path0 = https://example.com/path0
110 > path1:pushurl = https://example.com/path1
118 > path1:pushurl = https://example.com/path1
111 > EOF
119 > EOF
112 $ hg paths
120 $ hg paths
113 path0 = https://example.com/path0
121 path0 = https://example.com/path0
114
122
115 unknown sub-options aren't displayed
123 unknown sub-options aren't displayed
116
124
117 $ cat > .hg/hgrc << EOF
125 $ cat > .hg/hgrc << EOF
118 > [paths]
126 > [paths]
119 > path0 = https://example.com/path0
127 > path0 = https://example.com/path0
120 > path0:foo = https://example.com/path1
128 > path0:foo = https://example.com/path1
121 > EOF
129 > EOF
122
130
123 $ hg paths
131 $ hg paths
124 path0 = https://example.com/path0
132 path0 = https://example.com/path0
125
133
126 :pushurl must be a URL
134 :pushurl must be a URL
127
135
128 $ cat > .hg/hgrc << EOF
136 $ cat > .hg/hgrc << EOF
129 > [paths]
137 > [paths]
130 > default = /path/to/nothing
138 > default = /path/to/nothing
131 > default:pushurl = /not/a/url
139 > default:pushurl = /not/a/url
132 > EOF
140 > EOF
133
141
134 $ hg paths
142 $ hg paths
135 (paths.default:pushurl not a URL; ignoring)
143 (paths.default:pushurl not a URL; ignoring)
136 default = /path/to/nothing
144 default = /path/to/nothing
137
145
138 #fragment is not allowed in :pushurl
146 #fragment is not allowed in :pushurl
139
147
140 $ cat > .hg/hgrc << EOF
148 $ cat > .hg/hgrc << EOF
141 > [paths]
149 > [paths]
142 > default = https://example.com/repo
150 > default = https://example.com/repo
143 > invalid = https://example.com/repo
151 > invalid = https://example.com/repo
144 > invalid:pushurl = https://example.com/repo#branch
152 > invalid:pushurl = https://example.com/repo#branch
145 > EOF
153 > EOF
146
154
147 $ hg paths
155 $ hg paths
148 ("#fragment" in paths.invalid:pushurl not supported; ignoring)
156 ("#fragment" in paths.invalid:pushurl not supported; ignoring)
149 default = https://example.com/repo
157 default = https://example.com/repo
150 invalid = https://example.com/repo
158 invalid = https://example.com/repo
151 invalid:pushurl = https://example.com/repo
159 invalid:pushurl = https://example.com/repo
152
160
153 $ cd ..
161 $ cd ..
154
162
155 'file:' disables [paths] entries for clone destination
163 'file:' disables [paths] entries for clone destination
156
164
157 $ cat >> $HGRCPATH <<EOF
165 $ cat >> $HGRCPATH <<EOF
158 > [paths]
166 > [paths]
159 > gpath1 = http://hg.example.com
167 > gpath1 = http://hg.example.com
160 > EOF
168 > EOF
161
169
162 $ hg clone a gpath1
170 $ hg clone a gpath1
163 abort: cannot create new http repository
171 abort: cannot create new http repository
164 [255]
172 [255]
165
173
166 $ hg clone a file:gpath1
174 $ hg clone a file:gpath1
167 updating to branch default
175 updating to branch default
168 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 $ cd gpath1
177 $ cd gpath1
170 $ hg -q id
178 $ hg -q id
171 000000000000
179 000000000000
172
180
173 $ cd ..
181 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now