##// END OF EJS Templates
url: expand path for web.cacerts
Eduard-Cristian Stefan -
r13231:b335882c stable
parent child Browse files
Show More
@@ -1,556 +1,556 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import hex, nullid, nullrev, short
11 from node import hex, nullid, nullrev, short
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 import lock, util, extensions, error, encoding, node
13 import lock, util, extensions, error, encoding, node
14 import cmdutil, discovery, url
14 import cmdutil, discovery, url
15 import merge as mergemod
15 import merge as mergemod
16 import verify as verifymod
16 import verify as verifymod
17 import errno, os, shutil
17 import errno, os, shutil
18
18
19 def _local(path):
19 def _local(path):
20 path = util.expandpath(util.drop_scheme('file', path))
20 path = util.expandpath(util.drop_scheme('file', path))
21 return (os.path.isfile(path) and bundlerepo or localrepo)
21 return (os.path.isfile(path) and bundlerepo or localrepo)
22
22
23 def addbranchrevs(lrepo, repo, branches, revs):
23 def addbranchrevs(lrepo, repo, branches, revs):
24 hashbranch, branches = branches
24 hashbranch, branches = branches
25 if not hashbranch and not branches:
25 if not hashbranch and not branches:
26 return revs or None, revs and revs[0] or None
26 return revs or None, revs and revs[0] or None
27 revs = revs and list(revs) or []
27 revs = revs and list(revs) or []
28 if not repo.capable('branchmap'):
28 if not repo.capable('branchmap'):
29 if branches:
29 if branches:
30 raise util.Abort(_("remote branch lookup not supported"))
30 raise util.Abort(_("remote branch lookup not supported"))
31 revs.append(hashbranch)
31 revs.append(hashbranch)
32 return revs, revs[0]
32 return revs, revs[0]
33 branchmap = repo.branchmap()
33 branchmap = repo.branchmap()
34
34
35 def primary(butf8):
35 def primary(butf8):
36 if butf8 == '.':
36 if butf8 == '.':
37 if not lrepo or not lrepo.local():
37 if not lrepo or not lrepo.local():
38 raise util.Abort(_("dirstate branch not accessible"))
38 raise util.Abort(_("dirstate branch not accessible"))
39 butf8 = lrepo.dirstate.branch()
39 butf8 = lrepo.dirstate.branch()
40 if butf8 in branchmap:
40 if butf8 in branchmap:
41 revs.extend(node.hex(r) for r in reversed(branchmap[butf8]))
41 revs.extend(node.hex(r) for r in reversed(branchmap[butf8]))
42 return True
42 return True
43 else:
43 else:
44 return False
44 return False
45
45
46 for branch in branches:
46 for branch in branches:
47 butf8 = encoding.fromlocal(branch)
47 butf8 = encoding.fromlocal(branch)
48 if not primary(butf8):
48 if not primary(butf8):
49 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
49 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
50 if hashbranch:
50 if hashbranch:
51 butf8 = encoding.fromlocal(hashbranch)
51 butf8 = encoding.fromlocal(hashbranch)
52 if not primary(butf8):
52 if not primary(butf8):
53 revs.append(hashbranch)
53 revs.append(hashbranch)
54 return revs, revs[0]
54 return revs, revs[0]
55
55
56 def parseurl(url, branches=None):
56 def parseurl(url, branches=None):
57 '''parse url#branch, returning (url, (branch, branches))'''
57 '''parse url#branch, returning (url, (branch, branches))'''
58
58
59 if '#' not in url:
59 if '#' not in url:
60 return url, (None, branches or [])
60 return url, (None, branches or [])
61 url, branch = url.split('#', 1)
61 url, branch = url.split('#', 1)
62 return url, (branch, branches or [])
62 return url, (branch, branches or [])
63
63
64 schemes = {
64 schemes = {
65 'bundle': bundlerepo,
65 'bundle': bundlerepo,
66 'file': _local,
66 'file': _local,
67 'http': httprepo,
67 'http': httprepo,
68 'https': httprepo,
68 'https': httprepo,
69 'ssh': sshrepo,
69 'ssh': sshrepo,
70 'static-http': statichttprepo,
70 'static-http': statichttprepo,
71 }
71 }
72
72
73 def _lookup(path):
73 def _lookup(path):
74 scheme = 'file'
74 scheme = 'file'
75 if path:
75 if path:
76 c = path.find(':')
76 c = path.find(':')
77 if c > 0:
77 if c > 0:
78 scheme = path[:c]
78 scheme = path[:c]
79 thing = schemes.get(scheme) or schemes['file']
79 thing = schemes.get(scheme) or schemes['file']
80 try:
80 try:
81 return thing(path)
81 return thing(path)
82 except TypeError:
82 except TypeError:
83 return thing
83 return thing
84
84
85 def islocal(repo):
85 def islocal(repo):
86 '''return true if repo or path is local'''
86 '''return true if repo or path is local'''
87 if isinstance(repo, str):
87 if isinstance(repo, str):
88 try:
88 try:
89 return _lookup(repo).islocal(repo)
89 return _lookup(repo).islocal(repo)
90 except AttributeError:
90 except AttributeError:
91 return False
91 return False
92 return repo.local()
92 return repo.local()
93
93
94 def repository(ui, path='', create=False):
94 def repository(ui, path='', create=False):
95 """return a repository object for the specified path"""
95 """return a repository object for the specified path"""
96 repo = _lookup(path).instance(ui, path, create)
96 repo = _lookup(path).instance(ui, path, create)
97 ui = getattr(repo, "ui", ui)
97 ui = getattr(repo, "ui", ui)
98 for name, module in extensions.extensions():
98 for name, module in extensions.extensions():
99 hook = getattr(module, 'reposetup', None)
99 hook = getattr(module, 'reposetup', None)
100 if hook:
100 if hook:
101 hook(ui, repo)
101 hook(ui, repo)
102 return repo
102 return repo
103
103
104 def defaultdest(source):
104 def defaultdest(source):
105 '''return default destination of clone if none is given'''
105 '''return default destination of clone if none is given'''
106 return os.path.basename(os.path.normpath(source))
106 return os.path.basename(os.path.normpath(source))
107
107
108 def localpath(path):
108 def localpath(path):
109 if path.startswith('file://localhost/'):
109 if path.startswith('file://localhost/'):
110 return path[16:]
110 return path[16:]
111 if path.startswith('file://'):
111 if path.startswith('file://'):
112 return path[7:]
112 return path[7:]
113 if path.startswith('file:'):
113 if path.startswith('file:'):
114 return path[5:]
114 return path[5:]
115 return path
115 return path
116
116
117 def share(ui, source, dest=None, update=True):
117 def share(ui, source, dest=None, update=True):
118 '''create a shared repository'''
118 '''create a shared repository'''
119
119
120 if not islocal(source):
120 if not islocal(source):
121 raise util.Abort(_('can only share local repositories'))
121 raise util.Abort(_('can only share local repositories'))
122
122
123 if not dest:
123 if not dest:
124 dest = defaultdest(source)
124 dest = defaultdest(source)
125 else:
125 else:
126 dest = ui.expandpath(dest)
126 dest = ui.expandpath(dest)
127
127
128 if isinstance(source, str):
128 if isinstance(source, str):
129 origsource = ui.expandpath(source)
129 origsource = ui.expandpath(source)
130 source, branches = parseurl(origsource)
130 source, branches = parseurl(origsource)
131 srcrepo = repository(ui, source)
131 srcrepo = repository(ui, source)
132 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
132 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
133 else:
133 else:
134 srcrepo = source
134 srcrepo = source
135 origsource = source = srcrepo.url()
135 origsource = source = srcrepo.url()
136 checkout = None
136 checkout = None
137
137
138 sharedpath = srcrepo.sharedpath # if our source is already sharing
138 sharedpath = srcrepo.sharedpath # if our source is already sharing
139
139
140 root = os.path.realpath(dest)
140 root = os.path.realpath(dest)
141 roothg = os.path.join(root, '.hg')
141 roothg = os.path.join(root, '.hg')
142
142
143 if os.path.exists(roothg):
143 if os.path.exists(roothg):
144 raise util.Abort(_('destination already exists'))
144 raise util.Abort(_('destination already exists'))
145
145
146 if not os.path.isdir(root):
146 if not os.path.isdir(root):
147 os.mkdir(root)
147 os.mkdir(root)
148 os.mkdir(roothg)
148 os.mkdir(roothg)
149
149
150 requirements = ''
150 requirements = ''
151 try:
151 try:
152 requirements = srcrepo.opener('requires').read()
152 requirements = srcrepo.opener('requires').read()
153 except IOError, inst:
153 except IOError, inst:
154 if inst.errno != errno.ENOENT:
154 if inst.errno != errno.ENOENT:
155 raise
155 raise
156
156
157 requirements += 'shared\n'
157 requirements += 'shared\n'
158 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
158 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
159 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
159 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
160
160
161 default = srcrepo.ui.config('paths', 'default')
161 default = srcrepo.ui.config('paths', 'default')
162 if default:
162 if default:
163 f = file(os.path.join(roothg, 'hgrc'), 'w')
163 f = file(os.path.join(roothg, 'hgrc'), 'w')
164 f.write('[paths]\ndefault = %s\n' % default)
164 f.write('[paths]\ndefault = %s\n' % default)
165 f.close()
165 f.close()
166
166
167 r = repository(ui, root)
167 r = repository(ui, root)
168
168
169 if update:
169 if update:
170 r.ui.status(_("updating working directory\n"))
170 r.ui.status(_("updating working directory\n"))
171 if update is not True:
171 if update is not True:
172 checkout = update
172 checkout = update
173 for test in (checkout, 'default', 'tip'):
173 for test in (checkout, 'default', 'tip'):
174 if test is None:
174 if test is None:
175 continue
175 continue
176 try:
176 try:
177 uprev = r.lookup(test)
177 uprev = r.lookup(test)
178 break
178 break
179 except error.RepoLookupError:
179 except error.RepoLookupError:
180 continue
180 continue
181 _update(r, uprev)
181 _update(r, uprev)
182
182
183 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
183 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
184 stream=False, branch=None):
184 stream=False, branch=None):
185 """Make a copy of an existing repository.
185 """Make a copy of an existing repository.
186
186
187 Create a copy of an existing repository in a new directory. The
187 Create a copy of an existing repository in a new directory. The
188 source and destination are URLs, as passed to the repository
188 source and destination are URLs, as passed to the repository
189 function. Returns a pair of repository objects, the source and
189 function. Returns a pair of repository objects, the source and
190 newly created destination.
190 newly created destination.
191
191
192 The location of the source is added to the new repository's
192 The location of the source is added to the new repository's
193 .hg/hgrc file, as the default to be used for future pulls and
193 .hg/hgrc file, as the default to be used for future pulls and
194 pushes.
194 pushes.
195
195
196 If an exception is raised, the partly cloned/updated destination
196 If an exception is raised, the partly cloned/updated destination
197 repository will be deleted.
197 repository will be deleted.
198
198
199 Arguments:
199 Arguments:
200
200
201 source: repository object or URL
201 source: repository object or URL
202
202
203 dest: URL of destination repository to create (defaults to base
203 dest: URL of destination repository to create (defaults to base
204 name of source repository)
204 name of source repository)
205
205
206 pull: always pull from source repository, even in local case
206 pull: always pull from source repository, even in local case
207
207
208 stream: stream raw data uncompressed from repository (fast over
208 stream: stream raw data uncompressed from repository (fast over
209 LAN, slow over WAN)
209 LAN, slow over WAN)
210
210
211 rev: revision to clone up to (implies pull=True)
211 rev: revision to clone up to (implies pull=True)
212
212
213 update: update working directory after clone completes, if
213 update: update working directory after clone completes, if
214 destination is local repository (True means update to default rev,
214 destination is local repository (True means update to default rev,
215 anything else is treated as a revision)
215 anything else is treated as a revision)
216
216
217 branch: branches to clone
217 branch: branches to clone
218 """
218 """
219
219
220 if isinstance(source, str):
220 if isinstance(source, str):
221 origsource = ui.expandpath(source)
221 origsource = ui.expandpath(source)
222 source, branch = parseurl(origsource, branch)
222 source, branch = parseurl(origsource, branch)
223 src_repo = repository(ui, source)
223 src_repo = repository(ui, source)
224 else:
224 else:
225 src_repo = source
225 src_repo = source
226 branch = (None, branch or [])
226 branch = (None, branch or [])
227 origsource = source = src_repo.url()
227 origsource = source = src_repo.url()
228 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
228 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
229
229
230 if dest is None:
230 if dest is None:
231 dest = defaultdest(source)
231 dest = defaultdest(source)
232 ui.status(_("destination directory: %s\n") % dest)
232 ui.status(_("destination directory: %s\n") % dest)
233 else:
233 else:
234 dest = ui.expandpath(dest)
234 dest = ui.expandpath(dest)
235
235
236 dest = localpath(dest)
236 dest = localpath(dest)
237 source = localpath(source)
237 source = localpath(source)
238
238
239 if os.path.exists(dest):
239 if os.path.exists(dest):
240 if not os.path.isdir(dest):
240 if not os.path.isdir(dest):
241 raise util.Abort(_("destination '%s' already exists") % dest)
241 raise util.Abort(_("destination '%s' already exists") % dest)
242 elif os.listdir(dest):
242 elif os.listdir(dest):
243 raise util.Abort(_("destination '%s' is not empty") % dest)
243 raise util.Abort(_("destination '%s' is not empty") % dest)
244
244
245 class DirCleanup(object):
245 class DirCleanup(object):
246 def __init__(self, dir_):
246 def __init__(self, dir_):
247 self.rmtree = shutil.rmtree
247 self.rmtree = shutil.rmtree
248 self.dir_ = dir_
248 self.dir_ = dir_
249 def close(self):
249 def close(self):
250 self.dir_ = None
250 self.dir_ = None
251 def cleanup(self):
251 def cleanup(self):
252 if self.dir_:
252 if self.dir_:
253 self.rmtree(self.dir_, True)
253 self.rmtree(self.dir_, True)
254
254
255 src_lock = dest_lock = dir_cleanup = None
255 src_lock = dest_lock = dir_cleanup = None
256 try:
256 try:
257 if islocal(dest):
257 if islocal(dest):
258 dir_cleanup = DirCleanup(dest)
258 dir_cleanup = DirCleanup(dest)
259
259
260 abspath = origsource
260 abspath = origsource
261 copy = False
261 copy = False
262 if src_repo.cancopy() and islocal(dest):
262 if src_repo.cancopy() and islocal(dest):
263 abspath = os.path.abspath(util.drop_scheme('file', origsource))
263 abspath = os.path.abspath(util.drop_scheme('file', origsource))
264 copy = not pull and not rev
264 copy = not pull and not rev
265
265
266 if copy:
266 if copy:
267 try:
267 try:
268 # we use a lock here because if we race with commit, we
268 # we use a lock here because if we race with commit, we
269 # can end up with extra data in the cloned revlogs that's
269 # can end up with extra data in the cloned revlogs that's
270 # not pointed to by changesets, thus causing verify to
270 # not pointed to by changesets, thus causing verify to
271 # fail
271 # fail
272 src_lock = src_repo.lock(wait=False)
272 src_lock = src_repo.lock(wait=False)
273 except error.LockError:
273 except error.LockError:
274 copy = False
274 copy = False
275
275
276 if copy:
276 if copy:
277 src_repo.hook('preoutgoing', throw=True, source='clone')
277 src_repo.hook('preoutgoing', throw=True, source='clone')
278 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
278 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
279 if not os.path.exists(dest):
279 if not os.path.exists(dest):
280 os.mkdir(dest)
280 os.mkdir(dest)
281 else:
281 else:
282 # only clean up directories we create ourselves
282 # only clean up directories we create ourselves
283 dir_cleanup.dir_ = hgdir
283 dir_cleanup.dir_ = hgdir
284 try:
284 try:
285 dest_path = hgdir
285 dest_path = hgdir
286 os.mkdir(dest_path)
286 os.mkdir(dest_path)
287 except OSError, inst:
287 except OSError, inst:
288 if inst.errno == errno.EEXIST:
288 if inst.errno == errno.EEXIST:
289 dir_cleanup.close()
289 dir_cleanup.close()
290 raise util.Abort(_("destination '%s' already exists")
290 raise util.Abort(_("destination '%s' already exists")
291 % dest)
291 % dest)
292 raise
292 raise
293
293
294 hardlink = None
294 hardlink = None
295 num = 0
295 num = 0
296 for f in src_repo.store.copylist():
296 for f in src_repo.store.copylist():
297 src = os.path.join(src_repo.sharedpath, f)
297 src = os.path.join(src_repo.sharedpath, f)
298 dst = os.path.join(dest_path, f)
298 dst = os.path.join(dest_path, f)
299 dstbase = os.path.dirname(dst)
299 dstbase = os.path.dirname(dst)
300 if dstbase and not os.path.exists(dstbase):
300 if dstbase and not os.path.exists(dstbase):
301 os.mkdir(dstbase)
301 os.mkdir(dstbase)
302 if os.path.exists(src):
302 if os.path.exists(src):
303 if dst.endswith('data'):
303 if dst.endswith('data'):
304 # lock to avoid premature writing to the target
304 # lock to avoid premature writing to the target
305 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
305 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
306 hardlink, n = util.copyfiles(src, dst, hardlink)
306 hardlink, n = util.copyfiles(src, dst, hardlink)
307 num += n
307 num += n
308 if hardlink:
308 if hardlink:
309 ui.debug("linked %d files\n" % num)
309 ui.debug("linked %d files\n" % num)
310 else:
310 else:
311 ui.debug("copied %d files\n" % num)
311 ui.debug("copied %d files\n" % num)
312
312
313 # we need to re-init the repo after manually copying the data
313 # we need to re-init the repo after manually copying the data
314 # into it
314 # into it
315 dest_repo = repository(ui, dest)
315 dest_repo = repository(ui, dest)
316 src_repo.hook('outgoing', source='clone',
316 src_repo.hook('outgoing', source='clone',
317 node=node.hex(node.nullid))
317 node=node.hex(node.nullid))
318 else:
318 else:
319 try:
319 try:
320 dest_repo = repository(ui, dest, create=True)
320 dest_repo = repository(ui, dest, create=True)
321 except OSError, inst:
321 except OSError, inst:
322 if inst.errno == errno.EEXIST:
322 if inst.errno == errno.EEXIST:
323 dir_cleanup.close()
323 dir_cleanup.close()
324 raise util.Abort(_("destination '%s' already exists")
324 raise util.Abort(_("destination '%s' already exists")
325 % dest)
325 % dest)
326 raise
326 raise
327
327
328 revs = None
328 revs = None
329 if rev:
329 if rev:
330 if 'lookup' not in src_repo.capabilities:
330 if 'lookup' not in src_repo.capabilities:
331 raise util.Abort(_("src repository does not support "
331 raise util.Abort(_("src repository does not support "
332 "revision lookup and so doesn't "
332 "revision lookup and so doesn't "
333 "support clone by revision"))
333 "support clone by revision"))
334 revs = [src_repo.lookup(r) for r in rev]
334 revs = [src_repo.lookup(r) for r in rev]
335 checkout = revs[0]
335 checkout = revs[0]
336 if dest_repo.local():
336 if dest_repo.local():
337 dest_repo.clone(src_repo, heads=revs, stream=stream)
337 dest_repo.clone(src_repo, heads=revs, stream=stream)
338 elif src_repo.local():
338 elif src_repo.local():
339 src_repo.push(dest_repo, revs=revs)
339 src_repo.push(dest_repo, revs=revs)
340 else:
340 else:
341 raise util.Abort(_("clone from remote to remote not supported"))
341 raise util.Abort(_("clone from remote to remote not supported"))
342
342
343 if dir_cleanup:
343 if dir_cleanup:
344 dir_cleanup.close()
344 dir_cleanup.close()
345
345
346 if dest_repo.local():
346 if dest_repo.local():
347 fp = dest_repo.opener("hgrc", "w", text=True)
347 fp = dest_repo.opener("hgrc", "w", text=True)
348 fp.write("[paths]\n")
348 fp.write("[paths]\n")
349 fp.write("default = %s\n" % abspath)
349 fp.write("default = %s\n" % abspath)
350 fp.close()
350 fp.close()
351
351
352 dest_repo.ui.setconfig('paths', 'default', abspath)
352 dest_repo.ui.setconfig('paths', 'default', abspath)
353
353
354 if update:
354 if update:
355 if update is not True:
355 if update is not True:
356 checkout = update
356 checkout = update
357 if src_repo.local():
357 if src_repo.local():
358 checkout = src_repo.lookup(update)
358 checkout = src_repo.lookup(update)
359 for test in (checkout, 'default', 'tip'):
359 for test in (checkout, 'default', 'tip'):
360 if test is None:
360 if test is None:
361 continue
361 continue
362 try:
362 try:
363 uprev = dest_repo.lookup(test)
363 uprev = dest_repo.lookup(test)
364 break
364 break
365 except error.RepoLookupError:
365 except error.RepoLookupError:
366 continue
366 continue
367 bn = dest_repo[uprev].branch()
367 bn = dest_repo[uprev].branch()
368 dest_repo.ui.status(_("updating to branch %s\n")
368 dest_repo.ui.status(_("updating to branch %s\n")
369 % encoding.tolocal(bn))
369 % encoding.tolocal(bn))
370 _update(dest_repo, uprev)
370 _update(dest_repo, uprev)
371
371
372 return src_repo, dest_repo
372 return src_repo, dest_repo
373 finally:
373 finally:
374 release(src_lock, dest_lock)
374 release(src_lock, dest_lock)
375 if dir_cleanup is not None:
375 if dir_cleanup is not None:
376 dir_cleanup.cleanup()
376 dir_cleanup.cleanup()
377
377
378 def _showstats(repo, stats):
378 def _showstats(repo, stats):
379 repo.ui.status(_("%d files updated, %d files merged, "
379 repo.ui.status(_("%d files updated, %d files merged, "
380 "%d files removed, %d files unresolved\n") % stats)
380 "%d files removed, %d files unresolved\n") % stats)
381
381
382 def update(repo, node):
382 def update(repo, node):
383 """update the working directory to node, merging linear changes"""
383 """update the working directory to node, merging linear changes"""
384 stats = mergemod.update(repo, node, False, False, None)
384 stats = mergemod.update(repo, node, False, False, None)
385 _showstats(repo, stats)
385 _showstats(repo, stats)
386 if stats[3]:
386 if stats[3]:
387 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
387 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
388 return stats[3] > 0
388 return stats[3] > 0
389
389
390 # naming conflict in clone()
390 # naming conflict in clone()
391 _update = update
391 _update = update
392
392
393 def clean(repo, node, show_stats=True):
393 def clean(repo, node, show_stats=True):
394 """forcibly switch the working directory to node, clobbering changes"""
394 """forcibly switch the working directory to node, clobbering changes"""
395 stats = mergemod.update(repo, node, False, True, None)
395 stats = mergemod.update(repo, node, False, True, None)
396 if show_stats:
396 if show_stats:
397 _showstats(repo, stats)
397 _showstats(repo, stats)
398 return stats[3] > 0
398 return stats[3] > 0
399
399
400 def merge(repo, node, force=None, remind=True):
400 def merge(repo, node, force=None, remind=True):
401 """branch merge with node, resolving changes"""
401 """branch merge with node, resolving changes"""
402 stats = mergemod.update(repo, node, True, force, False)
402 stats = mergemod.update(repo, node, True, force, False)
403 _showstats(repo, stats)
403 _showstats(repo, stats)
404 if stats[3]:
404 if stats[3]:
405 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
405 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
406 "or 'hg update -C .' to abandon\n"))
406 "or 'hg update -C .' to abandon\n"))
407 elif remind:
407 elif remind:
408 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
408 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
409 return stats[3] > 0
409 return stats[3] > 0
410
410
411 def _incoming(displaychlist, subreporecurse, ui, repo, source,
411 def _incoming(displaychlist, subreporecurse, ui, repo, source,
412 opts, buffered=False):
412 opts, buffered=False):
413 """
413 """
414 Helper for incoming / gincoming.
414 Helper for incoming / gincoming.
415 displaychlist gets called with
415 displaychlist gets called with
416 (remoterepo, incomingchangesetlist, displayer) parameters,
416 (remoterepo, incomingchangesetlist, displayer) parameters,
417 and is supposed to contain only code that can't be unified.
417 and is supposed to contain only code that can't be unified.
418 """
418 """
419 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
419 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
420 other = repository(remoteui(repo, opts), source)
420 other = repository(remoteui(repo, opts), source)
421 ui.status(_('comparing with %s\n') % url.hidepassword(source))
421 ui.status(_('comparing with %s\n') % url.hidepassword(source))
422 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
422 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
423
423
424 if revs:
424 if revs:
425 revs = [other.lookup(rev) for rev in revs]
425 revs = [other.lookup(rev) for rev in revs]
426 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
426 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
427 opts["bundle"], opts["force"])
427 opts["bundle"], opts["force"])
428 if incoming is None:
428 if incoming is None:
429 ui.status(_("no changes found\n"))
429 ui.status(_("no changes found\n"))
430 return subreporecurse()
430 return subreporecurse()
431
431
432 try:
432 try:
433 chlist = other.changelog.nodesbetween(incoming, revs)[0]
433 chlist = other.changelog.nodesbetween(incoming, revs)[0]
434 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
434 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
435
435
436 # XXX once graphlog extension makes it into core,
436 # XXX once graphlog extension makes it into core,
437 # should be replaced by a if graph/else
437 # should be replaced by a if graph/else
438 displaychlist(other, chlist, displayer)
438 displaychlist(other, chlist, displayer)
439
439
440 displayer.close()
440 displayer.close()
441 finally:
441 finally:
442 if hasattr(other, 'close'):
442 if hasattr(other, 'close'):
443 other.close()
443 other.close()
444 if bundle:
444 if bundle:
445 os.unlink(bundle)
445 os.unlink(bundle)
446 subreporecurse()
446 subreporecurse()
447 return 0 # exit code is zero since we found incoming changes
447 return 0 # exit code is zero since we found incoming changes
448
448
449 def incoming(ui, repo, source, opts):
449 def incoming(ui, repo, source, opts):
450 def subreporecurse():
450 def subreporecurse():
451 ret = 1
451 ret = 1
452 if opts.get('subrepos'):
452 if opts.get('subrepos'):
453 ctx = repo[None]
453 ctx = repo[None]
454 for subpath in sorted(ctx.substate):
454 for subpath in sorted(ctx.substate):
455 sub = ctx.sub(subpath)
455 sub = ctx.sub(subpath)
456 ret = min(ret, sub.incoming(ui, source, opts))
456 ret = min(ret, sub.incoming(ui, source, opts))
457 return ret
457 return ret
458
458
459 def display(other, chlist, displayer):
459 def display(other, chlist, displayer):
460 limit = cmdutil.loglimit(opts)
460 limit = cmdutil.loglimit(opts)
461 if opts.get('newest_first'):
461 if opts.get('newest_first'):
462 chlist.reverse()
462 chlist.reverse()
463 count = 0
463 count = 0
464 for n in chlist:
464 for n in chlist:
465 if limit is not None and count >= limit:
465 if limit is not None and count >= limit:
466 break
466 break
467 parents = [p for p in other.changelog.parents(n) if p != nullid]
467 parents = [p for p in other.changelog.parents(n) if p != nullid]
468 if opts.get('no_merges') and len(parents) == 2:
468 if opts.get('no_merges') and len(parents) == 2:
469 continue
469 continue
470 count += 1
470 count += 1
471 displayer.show(other[n])
471 displayer.show(other[n])
472 return _incoming(display, subreporecurse, ui, repo, source, opts)
472 return _incoming(display, subreporecurse, ui, repo, source, opts)
473
473
474 def _outgoing(ui, repo, dest, opts):
474 def _outgoing(ui, repo, dest, opts):
475 dest = ui.expandpath(dest or 'default-push', dest or 'default')
475 dest = ui.expandpath(dest or 'default-push', dest or 'default')
476 dest, branches = parseurl(dest, opts.get('branch'))
476 dest, branches = parseurl(dest, opts.get('branch'))
477 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
477 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
478 if revs:
478 if revs:
479 revs = [repo.lookup(rev) for rev in revs]
479 revs = [repo.lookup(rev) for rev in revs]
480
480
481 other = repository(remoteui(repo, opts), dest)
481 other = repository(remoteui(repo, opts), dest)
482 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
482 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
483 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
483 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
484 if not o:
484 if not o:
485 ui.status(_("no changes found\n"))
485 ui.status(_("no changes found\n"))
486 return None
486 return None
487
487
488 return repo.changelog.nodesbetween(o, revs)[0]
488 return repo.changelog.nodesbetween(o, revs)[0]
489
489
490 def outgoing(ui, repo, dest, opts):
490 def outgoing(ui, repo, dest, opts):
491 def recurse():
491 def recurse():
492 ret = 1
492 ret = 1
493 if opts.get('subrepos'):
493 if opts.get('subrepos'):
494 ctx = repo[None]
494 ctx = repo[None]
495 for subpath in sorted(ctx.substate):
495 for subpath in sorted(ctx.substate):
496 sub = ctx.sub(subpath)
496 sub = ctx.sub(subpath)
497 ret = min(ret, sub.outgoing(ui, dest, opts))
497 ret = min(ret, sub.outgoing(ui, dest, opts))
498 return ret
498 return ret
499
499
500 limit = cmdutil.loglimit(opts)
500 limit = cmdutil.loglimit(opts)
501 o = _outgoing(ui, repo, dest, opts)
501 o = _outgoing(ui, repo, dest, opts)
502 if o is None:
502 if o is None:
503 return recurse()
503 return recurse()
504
504
505 if opts.get('newest_first'):
505 if opts.get('newest_first'):
506 o.reverse()
506 o.reverse()
507 displayer = cmdutil.show_changeset(ui, repo, opts)
507 displayer = cmdutil.show_changeset(ui, repo, opts)
508 count = 0
508 count = 0
509 for n in o:
509 for n in o:
510 if limit is not None and count >= limit:
510 if limit is not None and count >= limit:
511 break
511 break
512 parents = [p for p in repo.changelog.parents(n) if p != nullid]
512 parents = [p for p in repo.changelog.parents(n) if p != nullid]
513 if opts.get('no_merges') and len(parents) == 2:
513 if opts.get('no_merges') and len(parents) == 2:
514 continue
514 continue
515 count += 1
515 count += 1
516 displayer.show(repo[n])
516 displayer.show(repo[n])
517 displayer.close()
517 displayer.close()
518 recurse()
518 recurse()
519 return 0 # exit code is zero since we found outgoing changes
519 return 0 # exit code is zero since we found outgoing changes
520
520
521 def revert(repo, node, choose):
521 def revert(repo, node, choose):
522 """revert changes to revision in node without updating dirstate"""
522 """revert changes to revision in node without updating dirstate"""
523 return mergemod.update(repo, node, False, True, choose)[3] > 0
523 return mergemod.update(repo, node, False, True, choose)[3] > 0
524
524
525 def verify(repo):
525 def verify(repo):
526 """verify the consistency of a repository"""
526 """verify the consistency of a repository"""
527 return verifymod.verify(repo)
527 return verifymod.verify(repo)
528
528
529 def remoteui(src, opts):
529 def remoteui(src, opts):
530 'build a remote ui from ui or repo and opts'
530 'build a remote ui from ui or repo and opts'
531 if hasattr(src, 'baseui'): # looks like a repository
531 if hasattr(src, 'baseui'): # looks like a repository
532 dst = src.baseui.copy() # drop repo-specific config
532 dst = src.baseui.copy() # drop repo-specific config
533 src = src.ui # copy target options from repo
533 src = src.ui # copy target options from repo
534 else: # assume it's a global ui object
534 else: # assume it's a global ui object
535 dst = src.copy() # keep all global options
535 dst = src.copy() # keep all global options
536
536
537 # copy ssh-specific options
537 # copy ssh-specific options
538 for o in 'ssh', 'remotecmd':
538 for o in 'ssh', 'remotecmd':
539 v = opts.get(o) or src.config('ui', o)
539 v = opts.get(o) or src.config('ui', o)
540 if v:
540 if v:
541 dst.setconfig("ui", o, v)
541 dst.setconfig("ui", o, v)
542
542
543 # copy bundle-specific options
543 # copy bundle-specific options
544 r = src.config('bundle', 'mainreporoot')
544 r = src.config('bundle', 'mainreporoot')
545 if r:
545 if r:
546 dst.setconfig('bundle', 'mainreporoot', r)
546 dst.setconfig('bundle', 'mainreporoot', r)
547
547
548 # copy selected local settings to the remote ui
548 # copy selected local settings to the remote ui
549 for sect in ('auth', 'http_proxy'):
549 for sect in ('auth', 'http_proxy'):
550 for key, val in src.configitems(sect):
550 for key, val in src.configitems(sect):
551 dst.setconfig(sect, key, val)
551 dst.setconfig(sect, key, val)
552 v = src.config('web', 'cacerts')
552 v = src.config('web', 'cacerts')
553 if v:
553 if v:
554 dst.setconfig('web', 'cacerts', v)
554 dst.setconfig('web', 'cacerts', util.expandpath(v))
555
555
556 return dst
556 return dst
@@ -1,701 +1,703 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 import __builtin__
11 import __builtin__
12 from i18n import _
12 from i18n import _
13 import keepalive, util
13 import keepalive, util
14
14
15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
18 if (scheme and
18 if (scheme and
19 result.startswith(scheme + ':') and
19 result.startswith(scheme + ':') and
20 not result.startswith(scheme + '://') and
20 not result.startswith(scheme + '://') and
21 url.startswith(scheme + '://')
21 url.startswith(scheme + '://')
22 ):
22 ):
23 result = scheme + '://' + result[len(scheme + ':'):]
23 result = scheme + '://' + result[len(scheme + ':'):]
24 return result
24 return result
25
25
26 def hidepassword(url):
26 def hidepassword(url):
27 '''hide user credential in a url string'''
27 '''hide user credential in a url string'''
28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
31
31
32 def removeauth(url):
32 def removeauth(url):
33 '''remove all authentication information from a url string'''
33 '''remove all authentication information from a url string'''
34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
35 netloc = netloc[netloc.find('@')+1:]
35 netloc = netloc[netloc.find('@')+1:]
36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
37
37
38 def netlocsplit(netloc):
38 def netlocsplit(netloc):
39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
40
40
41 a = netloc.find('@')
41 a = netloc.find('@')
42 if a == -1:
42 if a == -1:
43 user, passwd = None, None
43 user, passwd = None, None
44 else:
44 else:
45 userpass, netloc = netloc[:a], netloc[a + 1:]
45 userpass, netloc = netloc[:a], netloc[a + 1:]
46 c = userpass.find(':')
46 c = userpass.find(':')
47 if c == -1:
47 if c == -1:
48 user, passwd = urllib.unquote(userpass), None
48 user, passwd = urllib.unquote(userpass), None
49 else:
49 else:
50 user = urllib.unquote(userpass[:c])
50 user = urllib.unquote(userpass[:c])
51 passwd = urllib.unquote(userpass[c + 1:])
51 passwd = urllib.unquote(userpass[c + 1:])
52 c = netloc.find(':')
52 c = netloc.find(':')
53 if c == -1:
53 if c == -1:
54 host, port = netloc, None
54 host, port = netloc, None
55 else:
55 else:
56 host, port = netloc[:c], netloc[c + 1:]
56 host, port = netloc[:c], netloc[c + 1:]
57 return host, port, user, passwd
57 return host, port, user, passwd
58
58
59 def netlocunsplit(host, port, user=None, passwd=None):
59 def netlocunsplit(host, port, user=None, passwd=None):
60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
61 if port:
61 if port:
62 hostport = host + ':' + port
62 hostport = host + ':' + port
63 else:
63 else:
64 hostport = host
64 hostport = host
65 if user:
65 if user:
66 quote = lambda s: urllib.quote(s, safe='')
66 quote = lambda s: urllib.quote(s, safe='')
67 if passwd:
67 if passwd:
68 userpass = quote(user) + ':' + quote(passwd)
68 userpass = quote(user) + ':' + quote(passwd)
69 else:
69 else:
70 userpass = quote(user)
70 userpass = quote(user)
71 return userpass + '@' + hostport
71 return userpass + '@' + hostport
72 return hostport
72 return hostport
73
73
74 _safe = ('abcdefghijklmnopqrstuvwxyz'
74 _safe = ('abcdefghijklmnopqrstuvwxyz'
75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
76 '0123456789' '_.-/')
76 '0123456789' '_.-/')
77 _safeset = None
77 _safeset = None
78 _hex = None
78 _hex = None
79 def quotepath(path):
79 def quotepath(path):
80 '''quote the path part of a URL
80 '''quote the path part of a URL
81
81
82 This is similar to urllib.quote, but it also tries to avoid
82 This is similar to urllib.quote, but it also tries to avoid
83 quoting things twice (inspired by wget):
83 quoting things twice (inspired by wget):
84
84
85 >>> quotepath('abc def')
85 >>> quotepath('abc def')
86 'abc%20def'
86 'abc%20def'
87 >>> quotepath('abc%20def')
87 >>> quotepath('abc%20def')
88 'abc%20def'
88 'abc%20def'
89 >>> quotepath('abc%20 def')
89 >>> quotepath('abc%20 def')
90 'abc%20%20def'
90 'abc%20%20def'
91 >>> quotepath('abc def%20')
91 >>> quotepath('abc def%20')
92 'abc%20def%20'
92 'abc%20def%20'
93 >>> quotepath('abc def%2')
93 >>> quotepath('abc def%2')
94 'abc%20def%252'
94 'abc%20def%252'
95 >>> quotepath('abc def%')
95 >>> quotepath('abc def%')
96 'abc%20def%25'
96 'abc%20def%25'
97 '''
97 '''
98 global _safeset, _hex
98 global _safeset, _hex
99 if _safeset is None:
99 if _safeset is None:
100 _safeset = set(_safe)
100 _safeset = set(_safe)
101 _hex = set('abcdefABCDEF0123456789')
101 _hex = set('abcdefABCDEF0123456789')
102 l = list(path)
102 l = list(path)
103 for i in xrange(len(l)):
103 for i in xrange(len(l)):
104 c = l[i]
104 c = l[i]
105 if (c == '%' and i + 2 < len(l) and
105 if (c == '%' and i + 2 < len(l) and
106 l[i + 1] in _hex and l[i + 2] in _hex):
106 l[i + 1] in _hex and l[i + 2] in _hex):
107 pass
107 pass
108 elif c not in _safeset:
108 elif c not in _safeset:
109 l[i] = '%%%02X' % ord(c)
109 l[i] = '%%%02X' % ord(c)
110 return ''.join(l)
110 return ''.join(l)
111
111
112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
113 def __init__(self, ui):
113 def __init__(self, ui):
114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
115 self.ui = ui
115 self.ui = ui
116
116
117 def find_user_password(self, realm, authuri):
117 def find_user_password(self, realm, authuri):
118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
119 self, realm, authuri)
119 self, realm, authuri)
120 user, passwd = authinfo
120 user, passwd = authinfo
121 if user and passwd:
121 if user and passwd:
122 self._writedebug(user, passwd)
122 self._writedebug(user, passwd)
123 return (user, passwd)
123 return (user, passwd)
124
124
125 if not user:
125 if not user:
126 auth = self.readauthtoken(authuri)
126 auth = self.readauthtoken(authuri)
127 if auth:
127 if auth:
128 user, passwd = auth.get('username'), auth.get('password')
128 user, passwd = auth.get('username'), auth.get('password')
129 if not user or not passwd:
129 if not user or not passwd:
130 if not self.ui.interactive():
130 if not self.ui.interactive():
131 raise util.Abort(_('http authorization required'))
131 raise util.Abort(_('http authorization required'))
132
132
133 self.ui.write(_("http authorization required\n"))
133 self.ui.write(_("http authorization required\n"))
134 self.ui.write(_("realm: %s\n") % realm)
134 self.ui.write(_("realm: %s\n") % realm)
135 if user:
135 if user:
136 self.ui.write(_("user: %s\n") % user)
136 self.ui.write(_("user: %s\n") % user)
137 else:
137 else:
138 user = self.ui.prompt(_("user:"), default=None)
138 user = self.ui.prompt(_("user:"), default=None)
139
139
140 if not passwd:
140 if not passwd:
141 passwd = self.ui.getpass()
141 passwd = self.ui.getpass()
142
142
143 self.add_password(realm, authuri, user, passwd)
143 self.add_password(realm, authuri, user, passwd)
144 self._writedebug(user, passwd)
144 self._writedebug(user, passwd)
145 return (user, passwd)
145 return (user, passwd)
146
146
147 def _writedebug(self, user, passwd):
147 def _writedebug(self, user, passwd):
148 msg = _('http auth: user %s, password %s\n')
148 msg = _('http auth: user %s, password %s\n')
149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
150
150
151 def readauthtoken(self, uri):
151 def readauthtoken(self, uri):
152 # Read configuration
152 # Read configuration
153 config = dict()
153 config = dict()
154 for key, val in self.ui.configitems('auth'):
154 for key, val in self.ui.configitems('auth'):
155 if '.' not in key:
155 if '.' not in key:
156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
157 continue
157 continue
158 group, setting = key.split('.', 1)
158 group, setting = key.split('.', 1)
159 gdict = config.setdefault(group, dict())
159 gdict = config.setdefault(group, dict())
160 if setting in ('username', 'cert', 'key'):
160 if setting in ('username', 'cert', 'key'):
161 val = util.expandpath(val)
161 val = util.expandpath(val)
162 gdict[setting] = val
162 gdict[setting] = val
163
163
164 # Find the best match
164 # Find the best match
165 scheme, hostpath = uri.split('://', 1)
165 scheme, hostpath = uri.split('://', 1)
166 bestlen = 0
166 bestlen = 0
167 bestauth = None
167 bestauth = None
168 for auth in config.itervalues():
168 for auth in config.itervalues():
169 prefix = auth.get('prefix')
169 prefix = auth.get('prefix')
170 if not prefix:
170 if not prefix:
171 continue
171 continue
172 p = prefix.split('://', 1)
172 p = prefix.split('://', 1)
173 if len(p) > 1:
173 if len(p) > 1:
174 schemes, prefix = [p[0]], p[1]
174 schemes, prefix = [p[0]], p[1]
175 else:
175 else:
176 schemes = (auth.get('schemes') or 'https').split()
176 schemes = (auth.get('schemes') or 'https').split()
177 if (prefix == '*' or hostpath.startswith(prefix)) and \
177 if (prefix == '*' or hostpath.startswith(prefix)) and \
178 len(prefix) > bestlen and scheme in schemes:
178 len(prefix) > bestlen and scheme in schemes:
179 bestlen = len(prefix)
179 bestlen = len(prefix)
180 bestauth = auth
180 bestauth = auth
181 return bestauth
181 return bestauth
182
182
183 class proxyhandler(urllib2.ProxyHandler):
183 class proxyhandler(urllib2.ProxyHandler):
184 def __init__(self, ui):
184 def __init__(self, ui):
185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
186 # XXX proxyauthinfo = None
186 # XXX proxyauthinfo = None
187
187
188 if proxyurl:
188 if proxyurl:
189 # proxy can be proper url or host[:port]
189 # proxy can be proper url or host[:port]
190 if not (proxyurl.startswith('http:') or
190 if not (proxyurl.startswith('http:') or
191 proxyurl.startswith('https:')):
191 proxyurl.startswith('https:')):
192 proxyurl = 'http://' + proxyurl + '/'
192 proxyurl = 'http://' + proxyurl + '/'
193 snpqf = urlparse.urlsplit(proxyurl)
193 snpqf = urlparse.urlsplit(proxyurl)
194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
195 hpup = netlocsplit(proxynetloc)
195 hpup = netlocsplit(proxynetloc)
196
196
197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
198 if not proxyuser:
198 if not proxyuser:
199 proxyuser = ui.config("http_proxy", "user")
199 proxyuser = ui.config("http_proxy", "user")
200 proxypasswd = ui.config("http_proxy", "passwd")
200 proxypasswd = ui.config("http_proxy", "passwd")
201
201
202 # see if we should use a proxy for this url
202 # see if we should use a proxy for this url
203 no_list = ["localhost", "127.0.0.1"]
203 no_list = ["localhost", "127.0.0.1"]
204 no_list.extend([p.lower() for
204 no_list.extend([p.lower() for
205 p in ui.configlist("http_proxy", "no")])
205 p in ui.configlist("http_proxy", "no")])
206 no_list.extend([p.strip().lower() for
206 no_list.extend([p.strip().lower() for
207 p in os.getenv("no_proxy", '').split(',')
207 p in os.getenv("no_proxy", '').split(',')
208 if p.strip()])
208 if p.strip()])
209 # "http_proxy.always" config is for running tests on localhost
209 # "http_proxy.always" config is for running tests on localhost
210 if ui.configbool("http_proxy", "always"):
210 if ui.configbool("http_proxy", "always"):
211 self.no_list = []
211 self.no_list = []
212 else:
212 else:
213 self.no_list = no_list
213 self.no_list = no_list
214
214
215 proxyurl = urlparse.urlunsplit((
215 proxyurl = urlparse.urlunsplit((
216 proxyscheme, netlocunsplit(proxyhost, proxyport,
216 proxyscheme, netlocunsplit(proxyhost, proxyport,
217 proxyuser, proxypasswd or ''),
217 proxyuser, proxypasswd or ''),
218 proxypath, proxyquery, proxyfrag))
218 proxypath, proxyquery, proxyfrag))
219 proxies = {'http': proxyurl, 'https': proxyurl}
219 proxies = {'http': proxyurl, 'https': proxyurl}
220 ui.debug('proxying through http://%s:%s\n' %
220 ui.debug('proxying through http://%s:%s\n' %
221 (proxyhost, proxyport))
221 (proxyhost, proxyport))
222 else:
222 else:
223 proxies = {}
223 proxies = {}
224
224
225 # urllib2 takes proxy values from the environment and those
225 # urllib2 takes proxy values from the environment and those
226 # will take precedence if found, so drop them
226 # will take precedence if found, so drop them
227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
228 try:
228 try:
229 if env in os.environ:
229 if env in os.environ:
230 del os.environ[env]
230 del os.environ[env]
231 except OSError:
231 except OSError:
232 pass
232 pass
233
233
234 urllib2.ProxyHandler.__init__(self, proxies)
234 urllib2.ProxyHandler.__init__(self, proxies)
235 self.ui = ui
235 self.ui = ui
236
236
237 def proxy_open(self, req, proxy, type_):
237 def proxy_open(self, req, proxy, type_):
238 host = req.get_host().split(':')[0]
238 host = req.get_host().split(':')[0]
239 if host in self.no_list:
239 if host in self.no_list:
240 return None
240 return None
241
241
242 # work around a bug in Python < 2.4.2
242 # work around a bug in Python < 2.4.2
243 # (it leaves a "\n" at the end of Proxy-authorization headers)
243 # (it leaves a "\n" at the end of Proxy-authorization headers)
244 baseclass = req.__class__
244 baseclass = req.__class__
245 class _request(baseclass):
245 class _request(baseclass):
246 def add_header(self, key, val):
246 def add_header(self, key, val):
247 if key.lower() == 'proxy-authorization':
247 if key.lower() == 'proxy-authorization':
248 val = val.strip()
248 val = val.strip()
249 return baseclass.add_header(self, key, val)
249 return baseclass.add_header(self, key, val)
250 req.__class__ = _request
250 req.__class__ = _request
251
251
252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
253
253
254 class httpsendfile(object):
254 class httpsendfile(object):
255 """This is a wrapper around the objects returned by python's "open".
255 """This is a wrapper around the objects returned by python's "open".
256
256
257 Its purpose is to send file-like objects via HTTP and, to do so, it
257 Its purpose is to send file-like objects via HTTP and, to do so, it
258 defines a __len__ attribute to feed the Content-Length header.
258 defines a __len__ attribute to feed the Content-Length header.
259 """
259 """
260
260
261 def __init__(self, *args, **kwargs):
261 def __init__(self, *args, **kwargs):
262 # We can't just "self._data = open(*args, **kwargs)" here because there
262 # We can't just "self._data = open(*args, **kwargs)" here because there
263 # is an "open" function defined in this module that shadows the global
263 # is an "open" function defined in this module that shadows the global
264 # one
264 # one
265 self._data = __builtin__.open(*args, **kwargs)
265 self._data = __builtin__.open(*args, **kwargs)
266 self.read = self._data.read
266 self.read = self._data.read
267 self.seek = self._data.seek
267 self.seek = self._data.seek
268 self.close = self._data.close
268 self.close = self._data.close
269 self.write = self._data.write
269 self.write = self._data.write
270
270
271 def __len__(self):
271 def __len__(self):
272 return os.fstat(self._data.fileno()).st_size
272 return os.fstat(self._data.fileno()).st_size
273
273
274 def _gen_sendfile(connection):
274 def _gen_sendfile(connection):
275 def _sendfile(self, data):
275 def _sendfile(self, data):
276 # send a file
276 # send a file
277 if isinstance(data, httpsendfile):
277 if isinstance(data, httpsendfile):
278 # if auth required, some data sent twice, so rewind here
278 # if auth required, some data sent twice, so rewind here
279 data.seek(0)
279 data.seek(0)
280 for chunk in util.filechunkiter(data):
280 for chunk in util.filechunkiter(data):
281 connection.send(self, chunk)
281 connection.send(self, chunk)
282 else:
282 else:
283 connection.send(self, data)
283 connection.send(self, data)
284 return _sendfile
284 return _sendfile
285
285
286 has_https = hasattr(urllib2, 'HTTPSHandler')
286 has_https = hasattr(urllib2, 'HTTPSHandler')
287 if has_https:
287 if has_https:
288 try:
288 try:
289 # avoid using deprecated/broken FakeSocket in python 2.6
289 # avoid using deprecated/broken FakeSocket in python 2.6
290 import ssl
290 import ssl
291 _ssl_wrap_socket = ssl.wrap_socket
291 _ssl_wrap_socket = ssl.wrap_socket
292 CERT_REQUIRED = ssl.CERT_REQUIRED
292 CERT_REQUIRED = ssl.CERT_REQUIRED
293 except ImportError:
293 except ImportError:
294 CERT_REQUIRED = 2
294 CERT_REQUIRED = 2
295
295
296 def _ssl_wrap_socket(sock, key_file, cert_file,
296 def _ssl_wrap_socket(sock, key_file, cert_file,
297 cert_reqs=CERT_REQUIRED, ca_certs=None):
297 cert_reqs=CERT_REQUIRED, ca_certs=None):
298 if ca_certs:
298 if ca_certs:
299 raise util.Abort(_(
299 raise util.Abort(_(
300 'certificate checking requires Python 2.6'))
300 'certificate checking requires Python 2.6'))
301
301
302 ssl = socket.ssl(sock, key_file, cert_file)
302 ssl = socket.ssl(sock, key_file, cert_file)
303 return httplib.FakeSocket(sock, ssl)
303 return httplib.FakeSocket(sock, ssl)
304
304
305 try:
305 try:
306 _create_connection = socket.create_connection
306 _create_connection = socket.create_connection
307 except AttributeError:
307 except AttributeError:
308 _GLOBAL_DEFAULT_TIMEOUT = object()
308 _GLOBAL_DEFAULT_TIMEOUT = object()
309
309
310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
311 source_address=None):
311 source_address=None):
312 # lifted from Python 2.6
312 # lifted from Python 2.6
313
313
314 msg = "getaddrinfo returns an empty list"
314 msg = "getaddrinfo returns an empty list"
315 host, port = address
315 host, port = address
316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
317 af, socktype, proto, canonname, sa = res
317 af, socktype, proto, canonname, sa = res
318 sock = None
318 sock = None
319 try:
319 try:
320 sock = socket.socket(af, socktype, proto)
320 sock = socket.socket(af, socktype, proto)
321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
322 sock.settimeout(timeout)
322 sock.settimeout(timeout)
323 if source_address:
323 if source_address:
324 sock.bind(source_address)
324 sock.bind(source_address)
325 sock.connect(sa)
325 sock.connect(sa)
326 return sock
326 return sock
327
327
328 except socket.error, msg:
328 except socket.error, msg:
329 if sock is not None:
329 if sock is not None:
330 sock.close()
330 sock.close()
331
331
332 raise socket.error, msg
332 raise socket.error, msg
333
333
334 class httpconnection(keepalive.HTTPConnection):
334 class httpconnection(keepalive.HTTPConnection):
335 # must be able to send big bundle as stream.
335 # must be able to send big bundle as stream.
336 send = _gen_sendfile(keepalive.HTTPConnection)
336 send = _gen_sendfile(keepalive.HTTPConnection)
337
337
338 def connect(self):
338 def connect(self):
339 if has_https and self.realhostport: # use CONNECT proxy
339 if has_https and self.realhostport: # use CONNECT proxy
340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
341 self.sock.connect((self.host, self.port))
341 self.sock.connect((self.host, self.port))
342 if _generic_proxytunnel(self):
342 if _generic_proxytunnel(self):
343 # we do not support client x509 certificates
343 # we do not support client x509 certificates
344 self.sock = _ssl_wrap_socket(self.sock, None, None)
344 self.sock = _ssl_wrap_socket(self.sock, None, None)
345 else:
345 else:
346 keepalive.HTTPConnection.connect(self)
346 keepalive.HTTPConnection.connect(self)
347
347
348 def getresponse(self):
348 def getresponse(self):
349 proxyres = getattr(self, 'proxyres', None)
349 proxyres = getattr(self, 'proxyres', None)
350 if proxyres:
350 if proxyres:
351 if proxyres.will_close:
351 if proxyres.will_close:
352 self.close()
352 self.close()
353 self.proxyres = None
353 self.proxyres = None
354 return proxyres
354 return proxyres
355 return keepalive.HTTPConnection.getresponse(self)
355 return keepalive.HTTPConnection.getresponse(self)
356
356
357 # general transaction handler to support different ways to handle
357 # general transaction handler to support different ways to handle
358 # HTTPS proxying before and after Python 2.6.3.
358 # HTTPS proxying before and after Python 2.6.3.
359 def _generic_start_transaction(handler, h, req):
359 def _generic_start_transaction(handler, h, req):
360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
361 tunnel_host = req._tunnel_host
361 tunnel_host = req._tunnel_host
362 if tunnel_host[:7] not in ['http://', 'https:/']:
362 if tunnel_host[:7] not in ['http://', 'https:/']:
363 tunnel_host = 'https://' + tunnel_host
363 tunnel_host = 'https://' + tunnel_host
364 new_tunnel = True
364 new_tunnel = True
365 else:
365 else:
366 tunnel_host = req.get_selector()
366 tunnel_host = req.get_selector()
367 new_tunnel = False
367 new_tunnel = False
368
368
369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
370 urlparts = urlparse.urlparse(tunnel_host)
370 urlparts = urlparse.urlparse(tunnel_host)
371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
372 realhostport = urlparts[1]
372 realhostport = urlparts[1]
373 if realhostport[-1] == ']' or ':' not in realhostport:
373 if realhostport[-1] == ']' or ':' not in realhostport:
374 realhostport += ':443'
374 realhostport += ':443'
375
375
376 h.realhostport = realhostport
376 h.realhostport = realhostport
377 h.headers = req.headers.copy()
377 h.headers = req.headers.copy()
378 h.headers.update(handler.parent.addheaders)
378 h.headers.update(handler.parent.addheaders)
379 return
379 return
380
380
381 h.realhostport = None
381 h.realhostport = None
382 h.headers = None
382 h.headers = None
383
383
384 def _generic_proxytunnel(self):
384 def _generic_proxytunnel(self):
385 proxyheaders = dict(
385 proxyheaders = dict(
386 [(x, self.headers[x]) for x in self.headers
386 [(x, self.headers[x]) for x in self.headers
387 if x.lower().startswith('proxy-')])
387 if x.lower().startswith('proxy-')])
388 self._set_hostport(self.host, self.port)
388 self._set_hostport(self.host, self.port)
389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
390 for header in proxyheaders.iteritems():
390 for header in proxyheaders.iteritems():
391 self.send('%s: %s\r\n' % header)
391 self.send('%s: %s\r\n' % header)
392 self.send('\r\n')
392 self.send('\r\n')
393
393
394 # majority of the following code is duplicated from
394 # majority of the following code is duplicated from
395 # httplib.HTTPConnection as there are no adequate places to
395 # httplib.HTTPConnection as there are no adequate places to
396 # override functions to provide the needed functionality
396 # override functions to provide the needed functionality
397 res = self.response_class(self.sock,
397 res = self.response_class(self.sock,
398 strict=self.strict,
398 strict=self.strict,
399 method=self._method)
399 method=self._method)
400
400
401 while True:
401 while True:
402 version, status, reason = res._read_status()
402 version, status, reason = res._read_status()
403 if status != httplib.CONTINUE:
403 if status != httplib.CONTINUE:
404 break
404 break
405 while True:
405 while True:
406 skip = res.fp.readline().strip()
406 skip = res.fp.readline().strip()
407 if not skip:
407 if not skip:
408 break
408 break
409 res.status = status
409 res.status = status
410 res.reason = reason.strip()
410 res.reason = reason.strip()
411
411
412 if res.status == 200:
412 if res.status == 200:
413 while True:
413 while True:
414 line = res.fp.readline()
414 line = res.fp.readline()
415 if line == '\r\n':
415 if line == '\r\n':
416 break
416 break
417 return True
417 return True
418
418
419 if version == 'HTTP/1.0':
419 if version == 'HTTP/1.0':
420 res.version = 10
420 res.version = 10
421 elif version.startswith('HTTP/1.'):
421 elif version.startswith('HTTP/1.'):
422 res.version = 11
422 res.version = 11
423 elif version == 'HTTP/0.9':
423 elif version == 'HTTP/0.9':
424 res.version = 9
424 res.version = 9
425 else:
425 else:
426 raise httplib.UnknownProtocol(version)
426 raise httplib.UnknownProtocol(version)
427
427
428 if res.version == 9:
428 if res.version == 9:
429 res.length = None
429 res.length = None
430 res.chunked = 0
430 res.chunked = 0
431 res.will_close = 1
431 res.will_close = 1
432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
433 return False
433 return False
434
434
435 res.msg = httplib.HTTPMessage(res.fp)
435 res.msg = httplib.HTTPMessage(res.fp)
436 res.msg.fp = None
436 res.msg.fp = None
437
437
438 # are we using the chunked-style of transfer encoding?
438 # are we using the chunked-style of transfer encoding?
439 trenc = res.msg.getheader('transfer-encoding')
439 trenc = res.msg.getheader('transfer-encoding')
440 if trenc and trenc.lower() == "chunked":
440 if trenc and trenc.lower() == "chunked":
441 res.chunked = 1
441 res.chunked = 1
442 res.chunk_left = None
442 res.chunk_left = None
443 else:
443 else:
444 res.chunked = 0
444 res.chunked = 0
445
445
446 # will the connection close at the end of the response?
446 # will the connection close at the end of the response?
447 res.will_close = res._check_close()
447 res.will_close = res._check_close()
448
448
449 # do we have a Content-Length?
449 # do we have a Content-Length?
450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
451 length = res.msg.getheader('content-length')
451 length = res.msg.getheader('content-length')
452 if length and not res.chunked:
452 if length and not res.chunked:
453 try:
453 try:
454 res.length = int(length)
454 res.length = int(length)
455 except ValueError:
455 except ValueError:
456 res.length = None
456 res.length = None
457 else:
457 else:
458 if res.length < 0: # ignore nonsensical negative lengths
458 if res.length < 0: # ignore nonsensical negative lengths
459 res.length = None
459 res.length = None
460 else:
460 else:
461 res.length = None
461 res.length = None
462
462
463 # does the body have a fixed length? (of zero)
463 # does the body have a fixed length? (of zero)
464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
465 100 <= status < 200 or # 1xx codes
465 100 <= status < 200 or # 1xx codes
466 res._method == 'HEAD'):
466 res._method == 'HEAD'):
467 res.length = 0
467 res.length = 0
468
468
469 # if the connection remains open, and we aren't using chunked, and
469 # if the connection remains open, and we aren't using chunked, and
470 # a content-length was not provided, then assume that the connection
470 # a content-length was not provided, then assume that the connection
471 # WILL close.
471 # WILL close.
472 if (not res.will_close and
472 if (not res.will_close and
473 not res.chunked and
473 not res.chunked and
474 res.length is None):
474 res.length is None):
475 res.will_close = 1
475 res.will_close = 1
476
476
477 self.proxyres = res
477 self.proxyres = res
478
478
479 return False
479 return False
480
480
481 class httphandler(keepalive.HTTPHandler):
481 class httphandler(keepalive.HTTPHandler):
482 def http_open(self, req):
482 def http_open(self, req):
483 return self.do_open(httpconnection, req)
483 return self.do_open(httpconnection, req)
484
484
485 def _start_transaction(self, h, req):
485 def _start_transaction(self, h, req):
486 _generic_start_transaction(self, h, req)
486 _generic_start_transaction(self, h, req)
487 return keepalive.HTTPHandler._start_transaction(self, h, req)
487 return keepalive.HTTPHandler._start_transaction(self, h, req)
488
488
489 def _verifycert(cert, hostname):
489 def _verifycert(cert, hostname):
490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
491 CRLs and subjectAltName are not handled.
491 CRLs and subjectAltName are not handled.
492
492
493 Returns error message if any problems are found and None on success.
493 Returns error message if any problems are found and None on success.
494 '''
494 '''
495 if not cert:
495 if not cert:
496 return _('no certificate received')
496 return _('no certificate received')
497 dnsname = hostname.lower()
497 dnsname = hostname.lower()
498 for s in cert.get('subject', []):
498 for s in cert.get('subject', []):
499 key, value = s[0]
499 key, value = s[0]
500 if key == 'commonName':
500 if key == 'commonName':
501 certname = value.lower()
501 certname = value.lower()
502 if (certname == dnsname or
502 if (certname == dnsname or
503 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
503 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
504 return None
504 return None
505 return _('certificate is for %s') % certname
505 return _('certificate is for %s') % certname
506 return _('no commonName found in certificate')
506 return _('no commonName found in certificate')
507
507
508 if has_https:
508 if has_https:
509 class BetterHTTPS(httplib.HTTPSConnection):
509 class BetterHTTPS(httplib.HTTPSConnection):
510 send = keepalive.safesend
510 send = keepalive.safesend
511
511
512 def connect(self):
512 def connect(self):
513 if hasattr(self, 'ui'):
513 if hasattr(self, 'ui'):
514 cacerts = self.ui.config('web', 'cacerts')
514 cacerts = self.ui.config('web', 'cacerts')
515 if cacerts:
516 cacerts = util.expandpath(cacerts)
515 else:
517 else:
516 cacerts = None
518 cacerts = None
517
519
518 if cacerts:
520 if cacerts:
519 sock = _create_connection((self.host, self.port))
521 sock = _create_connection((self.host, self.port))
520 self.sock = _ssl_wrap_socket(sock, self.key_file,
522 self.sock = _ssl_wrap_socket(sock, self.key_file,
521 self.cert_file, cert_reqs=CERT_REQUIRED,
523 self.cert_file, cert_reqs=CERT_REQUIRED,
522 ca_certs=cacerts)
524 ca_certs=cacerts)
523 msg = _verifycert(self.sock.getpeercert(), self.host)
525 msg = _verifycert(self.sock.getpeercert(), self.host)
524 if msg:
526 if msg:
525 raise util.Abort(_('%s certificate error: %s') %
527 raise util.Abort(_('%s certificate error: %s') %
526 (self.host, msg))
528 (self.host, msg))
527 self.ui.debug('%s certificate successfully verified\n' %
529 self.ui.debug('%s certificate successfully verified\n' %
528 self.host)
530 self.host)
529 else:
531 else:
530 self.ui.warn(_("warning: %s certificate not verified "
532 self.ui.warn(_("warning: %s certificate not verified "
531 "(check web.cacerts config setting)\n") %
533 "(check web.cacerts config setting)\n") %
532 self.host)
534 self.host)
533 httplib.HTTPSConnection.connect(self)
535 httplib.HTTPSConnection.connect(self)
534
536
535 class httpsconnection(BetterHTTPS):
537 class httpsconnection(BetterHTTPS):
536 response_class = keepalive.HTTPResponse
538 response_class = keepalive.HTTPResponse
537 # must be able to send big bundle as stream.
539 # must be able to send big bundle as stream.
538 send = _gen_sendfile(BetterHTTPS)
540 send = _gen_sendfile(BetterHTTPS)
539 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
541 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
540
542
541 def connect(self):
543 def connect(self):
542 if self.realhostport: # use CONNECT proxy
544 if self.realhostport: # use CONNECT proxy
543 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
545 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
544 self.sock.connect((self.host, self.port))
546 self.sock.connect((self.host, self.port))
545 if _generic_proxytunnel(self):
547 if _generic_proxytunnel(self):
546 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
548 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
547 self.cert_file)
549 self.cert_file)
548 else:
550 else:
549 BetterHTTPS.connect(self)
551 BetterHTTPS.connect(self)
550
552
551 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
553 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
552 def __init__(self, ui):
554 def __init__(self, ui):
553 keepalive.KeepAliveHandler.__init__(self)
555 keepalive.KeepAliveHandler.__init__(self)
554 urllib2.HTTPSHandler.__init__(self)
556 urllib2.HTTPSHandler.__init__(self)
555 self.ui = ui
557 self.ui = ui
556 self.pwmgr = passwordmgr(self.ui)
558 self.pwmgr = passwordmgr(self.ui)
557
559
558 def _start_transaction(self, h, req):
560 def _start_transaction(self, h, req):
559 _generic_start_transaction(self, h, req)
561 _generic_start_transaction(self, h, req)
560 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
562 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
561
563
562 def https_open(self, req):
564 def https_open(self, req):
563 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
565 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
564 return self.do_open(self._makeconnection, req)
566 return self.do_open(self._makeconnection, req)
565
567
566 def _makeconnection(self, host, port=None, *args, **kwargs):
568 def _makeconnection(self, host, port=None, *args, **kwargs):
567 keyfile = None
569 keyfile = None
568 certfile = None
570 certfile = None
569
571
570 if len(args) >= 1: # key_file
572 if len(args) >= 1: # key_file
571 keyfile = args[0]
573 keyfile = args[0]
572 if len(args) >= 2: # cert_file
574 if len(args) >= 2: # cert_file
573 certfile = args[1]
575 certfile = args[1]
574 args = args[2:]
576 args = args[2:]
575
577
576 # if the user has specified different key/cert files in
578 # if the user has specified different key/cert files in
577 # hgrc, we prefer these
579 # hgrc, we prefer these
578 if self.auth and 'key' in self.auth and 'cert' in self.auth:
580 if self.auth and 'key' in self.auth and 'cert' in self.auth:
579 keyfile = self.auth['key']
581 keyfile = self.auth['key']
580 certfile = self.auth['cert']
582 certfile = self.auth['cert']
581
583
582 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
584 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
583 conn.ui = self.ui
585 conn.ui = self.ui
584 return conn
586 return conn
585
587
586 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
588 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
587 def __init__(self, *args, **kwargs):
589 def __init__(self, *args, **kwargs):
588 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
590 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
589 self.retried_req = None
591 self.retried_req = None
590
592
591 def reset_retry_count(self):
593 def reset_retry_count(self):
592 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
594 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
593 # forever. We disable reset_retry_count completely and reset in
595 # forever. We disable reset_retry_count completely and reset in
594 # http_error_auth_reqed instead.
596 # http_error_auth_reqed instead.
595 pass
597 pass
596
598
597 def http_error_auth_reqed(self, auth_header, host, req, headers):
599 def http_error_auth_reqed(self, auth_header, host, req, headers):
598 # Reset the retry counter once for each request.
600 # Reset the retry counter once for each request.
599 if req is not self.retried_req:
601 if req is not self.retried_req:
600 self.retried_req = req
602 self.retried_req = req
601 self.retried = 0
603 self.retried = 0
602 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
604 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
603 # it doesn't know about the auth type requested. This can happen if
605 # it doesn't know about the auth type requested. This can happen if
604 # somebody is using BasicAuth and types a bad password.
606 # somebody is using BasicAuth and types a bad password.
605 try:
607 try:
606 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
608 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
607 self, auth_header, host, req, headers)
609 self, auth_header, host, req, headers)
608 except ValueError, inst:
610 except ValueError, inst:
609 arg = inst.args[0]
611 arg = inst.args[0]
610 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
612 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
611 return
613 return
612 raise
614 raise
613
615
614 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
616 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
615 def __init__(self, *args, **kwargs):
617 def __init__(self, *args, **kwargs):
616 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
618 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
617 self.retried_req = None
619 self.retried_req = None
618
620
619 def reset_retry_count(self):
621 def reset_retry_count(self):
620 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
622 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
621 # forever. We disable reset_retry_count completely and reset in
623 # forever. We disable reset_retry_count completely and reset in
622 # http_error_auth_reqed instead.
624 # http_error_auth_reqed instead.
623 pass
625 pass
624
626
625 def http_error_auth_reqed(self, auth_header, host, req, headers):
627 def http_error_auth_reqed(self, auth_header, host, req, headers):
626 # Reset the retry counter once for each request.
628 # Reset the retry counter once for each request.
627 if req is not self.retried_req:
629 if req is not self.retried_req:
628 self.retried_req = req
630 self.retried_req = req
629 self.retried = 0
631 self.retried = 0
630 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
632 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
631 self, auth_header, host, req, headers)
633 self, auth_header, host, req, headers)
632
634
633 def getauthinfo(path):
635 def getauthinfo(path):
634 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
636 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
635 if not urlpath:
637 if not urlpath:
636 urlpath = '/'
638 urlpath = '/'
637 if scheme != 'file':
639 if scheme != 'file':
638 # XXX: why are we quoting the path again with some smart
640 # XXX: why are we quoting the path again with some smart
639 # heuristic here? Anyway, it cannot be done with file://
641 # heuristic here? Anyway, it cannot be done with file://
640 # urls since path encoding is os/fs dependent (see
642 # urls since path encoding is os/fs dependent (see
641 # urllib.pathname2url() for details).
643 # urllib.pathname2url() for details).
642 urlpath = quotepath(urlpath)
644 urlpath = quotepath(urlpath)
643 host, port, user, passwd = netlocsplit(netloc)
645 host, port, user, passwd = netlocsplit(netloc)
644
646
645 # urllib cannot handle URLs with embedded user or passwd
647 # urllib cannot handle URLs with embedded user or passwd
646 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
648 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
647 urlpath, query, frag))
649 urlpath, query, frag))
648 if user:
650 if user:
649 netloc = host
651 netloc = host
650 if port:
652 if port:
651 netloc += ':' + port
653 netloc += ':' + port
652 # Python < 2.4.3 uses only the netloc to search for a password
654 # Python < 2.4.3 uses only the netloc to search for a password
653 authinfo = (None, (url, netloc), user, passwd or '')
655 authinfo = (None, (url, netloc), user, passwd or '')
654 else:
656 else:
655 authinfo = None
657 authinfo = None
656 return url, authinfo
658 return url, authinfo
657
659
658 handlerfuncs = []
660 handlerfuncs = []
659
661
660 def opener(ui, authinfo=None):
662 def opener(ui, authinfo=None):
661 '''
663 '''
662 construct an opener suitable for urllib2
664 construct an opener suitable for urllib2
663 authinfo will be added to the password manager
665 authinfo will be added to the password manager
664 '''
666 '''
665 handlers = [httphandler()]
667 handlers = [httphandler()]
666 if has_https:
668 if has_https:
667 handlers.append(httpshandler(ui))
669 handlers.append(httpshandler(ui))
668
670
669 handlers.append(proxyhandler(ui))
671 handlers.append(proxyhandler(ui))
670
672
671 passmgr = passwordmgr(ui)
673 passmgr = passwordmgr(ui)
672 if authinfo is not None:
674 if authinfo is not None:
673 passmgr.add_password(*authinfo)
675 passmgr.add_password(*authinfo)
674 user, passwd = authinfo[2:4]
676 user, passwd = authinfo[2:4]
675 ui.debug('http auth: user %s, password %s\n' %
677 ui.debug('http auth: user %s, password %s\n' %
676 (user, passwd and '*' * len(passwd) or 'not set'))
678 (user, passwd and '*' * len(passwd) or 'not set'))
677
679
678 handlers.extend((httpbasicauthhandler(passmgr),
680 handlers.extend((httpbasicauthhandler(passmgr),
679 httpdigestauthhandler(passmgr)))
681 httpdigestauthhandler(passmgr)))
680 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
682 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
681 opener = urllib2.build_opener(*handlers)
683 opener = urllib2.build_opener(*handlers)
682
684
683 # 1.0 here is the _protocol_ version
685 # 1.0 here is the _protocol_ version
684 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
686 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
685 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
687 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
686 return opener
688 return opener
687
689
688 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
690 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
689
691
690 def open(ui, url, data=None):
692 def open(ui, url, data=None):
691 scheme = None
693 scheme = None
692 m = scheme_re.search(url)
694 m = scheme_re.search(url)
693 if m:
695 if m:
694 scheme = m.group(1).lower()
696 scheme = m.group(1).lower()
695 if not scheme:
697 if not scheme:
696 path = util.normpath(os.path.abspath(url))
698 path = util.normpath(os.path.abspath(url))
697 url = 'file://' + urllib.pathname2url(path)
699 url = 'file://' + urllib.pathname2url(path)
698 authinfo = None
700 authinfo = None
699 else:
701 else:
700 url, authinfo = getauthinfo(url)
702 url, authinfo = getauthinfo(url)
701 return opener(ui, authinfo).open(url, data)
703 return opener(ui, authinfo).open(url, data)
@@ -1,189 +1,190 b''
1 Proper https client requires the built-in ssl from Python 2.6.
1 Proper https client requires the built-in ssl from Python 2.6.
2
2
3 $ "$TESTDIR/hghave" ssl || exit 80
3 $ "$TESTDIR/hghave" ssl || exit 80
4
4
5 Certificates created with:
5 Certificates created with:
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 Can be dumped with:
8 Can be dumped with:
9 openssl x509 -in pub.pem -text
9 openssl x509 -in pub.pem -text
10
10
11 $ cat << EOT > priv.pem
11 $ cat << EOT > priv.pem
12 > -----BEGIN PRIVATE KEY-----
12 > -----BEGIN PRIVATE KEY-----
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 > HY8gUVkVRVs=
20 > HY8gUVkVRVs=
21 > -----END PRIVATE KEY-----
21 > -----END PRIVATE KEY-----
22 > EOT
22 > EOT
23
23
24 $ cat << EOT > pub.pem
24 $ cat << EOT > pub.pem
25 > -----BEGIN CERTIFICATE-----
25 > -----BEGIN CERTIFICATE-----
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 > -----END CERTIFICATE-----
35 > -----END CERTIFICATE-----
36 > EOT
36 > EOT
37 $ cat priv.pem pub.pem >> server.pem
37 $ cat priv.pem pub.pem >> server.pem
38 $ PRIV=`pwd`/server.pem
38 $ PRIV=`pwd`/server.pem
39
39
40 $ cat << EOT > pub-other.pem
40 $ cat << EOT > pub-other.pem
41 > -----BEGIN CERTIFICATE-----
41 > -----BEGIN CERTIFICATE-----
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 > -----END CERTIFICATE-----
51 > -----END CERTIFICATE-----
52 > EOT
52 > EOT
53
53
54 pub.pem patched with other notBefore / notAfter:
54 pub.pem patched with other notBefore / notAfter:
55
55
56 $ cat << EOT > pub-not-yet.pem
56 $ cat << EOT > pub-not-yet.pem
57 > -----BEGIN CERTIFICATE-----
57 > -----BEGIN CERTIFICATE-----
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 > -----END CERTIFICATE-----
66 > -----END CERTIFICATE-----
67 > EOT
67 > EOT
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69
69
70 $ cat << EOT > pub-expired.pem
70 $ cat << EOT > pub-expired.pem
71 > -----BEGIN CERTIFICATE-----
71 > -----BEGIN CERTIFICATE-----
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 > -----END CERTIFICATE-----
80 > -----END CERTIFICATE-----
81 > EOT
81 > EOT
82 $ cat priv.pem pub-expired.pem > server-expired.pem
82 $ cat priv.pem pub-expired.pem > server-expired.pem
83
83
84 $ hg init test
84 $ hg init test
85 $ cd test
85 $ cd test
86 $ echo foo>foo
86 $ echo foo>foo
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 $ echo foo>foo.d/foo
88 $ echo foo>foo.d/foo
89 $ echo bar>foo.d/bAr.hg.d/BaR
89 $ echo bar>foo.d/bAr.hg.d/BaR
90 $ echo bar>foo.d/baR.d.hg/bAR
90 $ echo bar>foo.d/baR.d.hg/bAR
91 $ hg commit -A -m 1
91 $ hg commit -A -m 1
92 adding foo
92 adding foo
93 adding foo.d/bAr.hg.d/BaR
93 adding foo.d/bAr.hg.d/BaR
94 adding foo.d/baR.d.hg/bAR
94 adding foo.d/baR.d.hg/bAR
95 adding foo.d/foo
95 adding foo.d/foo
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 $ cat ../hg0.pid >> $DAEMON_PIDS
97 $ cat ../hg0.pid >> $DAEMON_PIDS
98
98
99 Test server address cannot be reused
99 Test server address cannot be reused
100
100
101 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
101 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
102 abort: cannot start server at ':$HGPORT': Address already in use
102 abort: cannot start server at ':$HGPORT': Address already in use
103 [255]
103 [255]
104 $ cd ..
104 $ cd ..
105
105
106 clone via pull
106 clone via pull
107
107
108 $ hg clone https://localhost:$HGPORT/ copy-pull
108 $ hg clone https://localhost:$HGPORT/ copy-pull
109 warning: localhost certificate not verified (check web.cacerts config setting)
109 warning: localhost certificate not verified (check web.cacerts config setting)
110 requesting all changes
110 requesting all changes
111 adding changesets
111 adding changesets
112 adding manifests
112 adding manifests
113 adding file changes
113 adding file changes
114 added 1 changesets with 4 changes to 4 files
114 added 1 changesets with 4 changes to 4 files
115 updating to branch default
115 updating to branch default
116 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 $ hg verify -R copy-pull
117 $ hg verify -R copy-pull
118 checking changesets
118 checking changesets
119 checking manifests
119 checking manifests
120 crosschecking files in changesets and manifests
120 crosschecking files in changesets and manifests
121 checking files
121 checking files
122 4 files, 1 changesets, 4 total revisions
122 4 files, 1 changesets, 4 total revisions
123 $ cd test
123 $ cd test
124 $ echo bar > bar
124 $ echo bar > bar
125 $ hg commit -A -d '1 0' -m 2
125 $ hg commit -A -d '1 0' -m 2
126 adding bar
126 adding bar
127 $ cd ..
127 $ cd ..
128
128
129 pull without cacert
129 pull without cacert
130
130
131 $ cd copy-pull
131 $ cd copy-pull
132 $ echo '[hooks]' >> .hg/hgrc
132 $ echo '[hooks]' >> .hg/hgrc
133 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
133 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
134 $ hg pull
134 $ hg pull
135 warning: localhost certificate not verified (check web.cacerts config setting)
135 warning: localhost certificate not verified (check web.cacerts config setting)
136 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
136 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
137 pulling from https://localhost:$HGPORT/
137 pulling from https://localhost:$HGPORT/
138 searching for changes
138 searching for changes
139 adding changesets
139 adding changesets
140 adding manifests
140 adding manifests
141 adding file changes
141 adding file changes
142 added 1 changesets with 1 changes to 1 files
142 added 1 changesets with 1 changes to 1 files
143 (run 'hg update' to get a working copy)
143 (run 'hg update' to get a working copy)
144 $ cd ..
144 $ cd ..
145
145
146 cacert configured in local repo
146 cacert configured in local repo
147
147
148 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
148 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
149 $ echo "[web]" >> copy-pull/.hg/hgrc
149 $ echo "[web]" >> copy-pull/.hg/hgrc
150 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
150 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
151 $ hg -R copy-pull pull --traceback
151 $ hg -R copy-pull pull --traceback
152 pulling from https://localhost:$HGPORT/
152 pulling from https://localhost:$HGPORT/
153 searching for changes
153 searching for changes
154 no changes found
154 no changes found
155 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
155 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
156
156
157 cacert configured globally
157 cacert configured globally, also testing expansion of environment
158 variables in the filename
158
159
159 $ echo "[web]" >> $HGRCPATH
160 $ echo "[web]" >> $HGRCPATH
160 $ echo "cacerts=`pwd`/pub.pem" >> $HGRCPATH
161 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
161 $ hg -R copy-pull pull
162 $ P=`pwd` hg -R copy-pull pull
162 pulling from https://localhost:$HGPORT/
163 pulling from https://localhost:$HGPORT/
163 searching for changes
164 searching for changes
164 no changes found
165 no changes found
165
166
166 cacert mismatch
167 cacert mismatch
167
168
168 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
169 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
169 abort: 127.0.0.1 certificate error: certificate is for localhost
170 abort: 127.0.0.1 certificate error: certificate is for localhost
170 [255]
171 [255]
171 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
172 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
172 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
173 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
173 [255]
174 [255]
174
175
175 Test server cert which isn't valid yet
176 Test server cert which isn't valid yet
176
177
177 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
178 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
178 $ cat hg1.pid >> $DAEMON_PIDS
179 $ cat hg1.pid >> $DAEMON_PIDS
179 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
180 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
180 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
181 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
181 [255]
182 [255]
182
183
183 Test server cert which no longer is valid
184 Test server cert which no longer is valid
184
185
185 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
186 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
186 $ cat hg2.pid >> $DAEMON_PIDS
187 $ cat hg2.pid >> $DAEMON_PIDS
187 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
188 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
188 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
189 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
189 [255]
190 [255]
General Comments 0
You need to be logged in to leave comments. Login now