Mercurial > hgsubversion
comparison svnwrap/svn_swig_wrapper.py @ 304:ce676eff002b
First merge, totally untested.
author | Dan Villiom Podlaski Christiansen <danchr@gmail.com> |
---|---|
date | Fri, 01 May 2009 10:28:59 +0200 |
parents | 79440ed81011 25d843281127 |
children | 97360cb777a8 |
comparison
equal
deleted
inserted
replaced
303:f423a8780832 | 304:ce676eff002b |
---|---|
2 import getpass | 2 import getpass |
3 import os | 3 import os |
4 import shutil | 4 import shutil |
5 import sys | 5 import sys |
6 import tempfile | 6 import tempfile |
7 import urlparse | |
8 import urllib | |
7 import hashlib | 9 import hashlib |
8 import collections | 10 import collections |
9 import gc | |
10 | 11 |
11 from svn import client | 12 from svn import client |
12 from svn import core | 13 from svn import core |
13 from svn import delta | 14 from svn import delta |
14 from svn import ra | 15 from svn import ra |
68 def _create_auth_baton(pool): | 69 def _create_auth_baton(pool): |
69 """Create a Subversion authentication baton. """ | 70 """Create a Subversion authentication baton. """ |
70 # Give the client context baton a suite of authentication | 71 # Give the client context baton a suite of authentication |
71 # providers.h | 72 # providers.h |
72 platform_specific = ['svn_auth_get_gnome_keyring_simple_provider', | 73 platform_specific = ['svn_auth_get_gnome_keyring_simple_provider', |
73 'svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider', | 74 'svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider', |
74 'svn_auth_get_keychain_simple_provider', | 75 'svn_auth_get_keychain_simple_provider', |
75 'svn_auth_get_keychain_ssl_client_cert_pw_provider', | 76 'svn_auth_get_keychain_ssl_client_cert_pw_provider', |
76 'svn_auth_get_kwallet_simple_provider', | 77 'svn_auth_get_kwallet_simple_provider', |
77 'svn_auth_get_kwallet_ssl_client_cert_pw_provider', | 78 'svn_auth_get_kwallet_ssl_client_cert_pw_provider', |
78 'svn_auth_get_ssl_client_cert_file_provider', | 79 'svn_auth_get_ssl_client_cert_file_provider', |
79 'svn_auth_get_windows_simple_provider', | 80 'svn_auth_get_windows_simple_provider', |
80 'svn_auth_get_windows_ssl_server_trust_provider', | 81 'svn_auth_get_windows_ssl_server_trust_provider', |
81 ] | 82 ] |
82 | 83 |
83 providers = [] | 84 providers = [] |
84 | 85 # Platform-dependant authentication methods |
85 for p in platform_specific: | 86 getprovider = getattr(core, 'svn_auth_get_platform_specific_provider', |
86 if hasattr(core, p): | 87 None) |
87 try: | 88 if getprovider: |
88 providers.append(getattr(core, p)()) | 89 # Available in svn >= 1.6 |
89 except RuntimeError: | 90 for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'): |
90 pass | 91 for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'): |
92 p = getprovider(name, type, pool) | |
93 if p: | |
94 providers.append(p) | |
95 else: | |
96 for p in platform_specific: | |
97 if hasattr(core, p): | |
98 try: | |
99 providers.append(getattr(core, p)()) | |
100 except RuntimeError: | |
101 pass | |
91 | 102 |
92 providers += [ | 103 providers += [ |
93 client.get_simple_provider(), | 104 client.get_simple_provider(), |
94 client.get_username_provider(), | 105 client.get_username_provider(), |
95 client.get_ssl_client_cert_file_provider(), | 106 client.get_ssl_client_cert_file_provider(), |
98 client.get_simple_prompt_provider(user_pass_prompt, 2), | 109 client.get_simple_prompt_provider(user_pass_prompt, 2), |
99 ] | 110 ] |
100 | 111 |
101 return core.svn_auth_open(providers, pool) | 112 return core.svn_auth_open(providers, pool) |
102 | 113 |
114 def parse_url(url): | |
115 """Parse a URL and return a tuple (username, password, url) | |
116 """ | |
117 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) | |
118 user, passwd = None, None | |
119 if '@' in netloc: | |
120 userpass, netloc = netloc.split('@') | |
121 if ':' in userpass: | |
122 user, passwd = userpass.split(':') | |
123 user, passwd = urllib.unquote(user) or None, urllib.unquote(passwd) or None | |
124 else: | |
125 user = urllib.unquote(userpass) or None | |
126 url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) | |
127 return (user, passwd, url) | |
103 | 128 |
104 class Revision(tuple): | 129 class Revision(tuple): |
105 """Wrapper for a Subversion revision. | 130 """Wrapper for a Subversion revision. |
106 | 131 |
107 Derives from tuple in an attempt to minimise the memory footprint. | 132 Derives from tuple in an attempt to minimise the memory footprint. |
146 """Wrapper for a Subversion repository. | 171 """Wrapper for a Subversion repository. |
147 | 172 |
148 This uses the SWIG Python bindings, and will only work on svn >= 1.4. | 173 This uses the SWIG Python bindings, and will only work on svn >= 1.4. |
149 It takes a required param, the URL. | 174 It takes a required param, the URL. |
150 """ | 175 """ |
151 def __init__(self, url='', username='', head=None): | 176 def __init__(self, url='', username='', password='', head=None): |
152 self.svn_url = url | 177 parsed = parse_url(url) |
153 self.uname = username | 178 # --username and --password override URL credentials |
179 self.username = username or parsed[0] | |
180 self.password = password or parsed[1] | |
181 self.svn_url = parsed[2] | |
154 self.auth_baton_pool = core.Pool() | 182 self.auth_baton_pool = core.Pool() |
155 self.auth_baton = _create_auth_baton(self.auth_baton_pool) | 183 self.auth_baton = _create_auth_baton(self.auth_baton_pool) |
156 # self.init_ra_and_client() assumes that a pool already exists | 184 # self.init_ra_and_client() assumes that a pool already exists |
157 self.pool = core.Pool() | 185 self.pool = core.Pool() |
158 | 186 |
167 | 195 |
168 def init_ra_and_client(self): | 196 def init_ra_and_client(self): |
169 """Initializes the RA and client layers, because sometimes getting | 197 """Initializes the RA and client layers, because sometimes getting |
170 unified diffs runs the remote server out of open files. | 198 unified diffs runs the remote server out of open files. |
171 """ | 199 """ |
172 # Debugging code; retained for possible later use. | 200 # while we're in here we'll recreate our pool |
173 if False: | |
174 gc.collect() | |
175 import pympler.muppy.tracker | |
176 try: | |
177 self.memory_tracker | |
178 try: | |
179 self.memory_tracker.print_diff(self.memory_base) | |
180 except: | |
181 print 'HOP' | |
182 self.memory_base = self.memory_tracker.create_summary() | |
183 except: | |
184 print 'HEP' | |
185 self.memory_tracker = pympler.muppy.tracker.SummaryTracker() | |
186 | |
187 # while we're in here we'll recreate our pool, but first, we clear it | |
188 # and destroy it to make possible leaks cause fatal errors. | |
189 self.pool.clear() | |
190 self.pool.destroy() | |
191 self.pool = core.Pool() | 201 self.pool = core.Pool() |
202 if self.username: | |
203 core.svn_auth_set_parameter(self.auth_baton, | |
204 core.SVN_AUTH_PARAM_DEFAULT_USERNAME, | |
205 self.username) | |
206 if self.password: | |
207 core.svn_auth_set_parameter(self.auth_baton, | |
208 core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, | |
209 self.password) | |
192 self.client_context = client.create_context() | 210 self.client_context = client.create_context() |
193 | 211 |
194 self.client_context.auth_baton = self.auth_baton | 212 self.client_context.auth_baton = self.auth_baton |
195 self.client_context.config = svn_config | 213 self.client_context.config = svn_config |
196 callbacks = RaCallbacks() | 214 callbacks = RaCallbacks() |
296 if hist.paths[path].copyfrom_path is None: | 314 if hist.paths[path].copyfrom_path is None: |
297 return None, None | 315 return None, None |
298 source = hist.paths[path].copyfrom_path | 316 source = hist.paths[path].copyfrom_path |
299 source_rev = 0 | 317 source_rev = 0 |
300 for p in hist.paths: | 318 for p in hist.paths: |
319 if not p.startswith(path): | |
320 continue | |
301 if hist.paths[p].copyfrom_rev: | 321 if hist.paths[p].copyfrom_rev: |
302 # We assume that the revision of the source tree as it was | 322 # We assume that the revision of the source tree as it was |
303 # copied was actually the revision of the highest revision | 323 # copied was actually the revision of the highest revision |
304 # copied item. This could be wrong, but in practice it will | 324 # copied item. This could be wrong, but in practice it will |
305 # *probably* be correct | 325 # *probably* be correct |
332 of revisions at a time. | 352 of revisions at a time. |
333 | 353 |
334 The reason this is lazy is so that you can use the same repo object | 354 The reason this is lazy is so that you can use the same repo object |
335 to perform RA calls to get deltas. | 355 to perform RA calls to get deltas. |
336 """ | 356 """ |
337 # NB: you'd think this would work, but you'd be wrong. I'm pretty | 357 return self.fetch_history_at_paths([''], start=start, |
338 # convinced there must be some kind of svn bug here. | |
339 #return self.fetch_history_at_paths(['tags', 'trunk', 'branches'], | |
340 # start=start) | |
341 # However, we no longer need such filtering, as we gracefully handle | |
342 # branches located at arbitrary locations. | |
343 return self.fetch_history_at_paths([''], start=start, stop=stop, | |
344 chunk_size=chunk_size) | 358 chunk_size=chunk_size) |
345 | 359 |
346 def fetch_history_at_paths(self, paths, start=None, stop=None, | 360 def fetch_history_at_paths(self, paths, start=None, stop=None, |
347 chunk_size=_chunk_size): | 361 chunk_size=_chunk_size): |
348 '''TODO: This method should be merged with self.revisions() as | 362 '''TODO: This method should be merged with self.revisions() as |
410 False, | 424 False, |
411 self.pool) | 425 self.pool) |
412 checksum = [] | 426 checksum = [] |
413 # internal dir batons can fall out of scope and get GCed before svn is | 427 # internal dir batons can fall out of scope and get GCed before svn is |
414 # done with them. This prevents that (credit to gvn for the idea). | 428 # done with them. This prevents that (credit to gvn for the idea). |
415 # TODO: verify that these are not the cause of our leaks | |
416 batons = [edit_baton, ] | 429 batons = [edit_baton, ] |
417 def driver_cb(parent, path, pool): | 430 def driver_cb(parent, path, pool): |
418 if not parent: | 431 if not parent: |
419 bat = editor.open_root(edit_baton, base_revision, self.pool) | 432 bat = editor.open_root(edit_baton, base_revision, self.pool) |
420 batons.append(bat) | 433 batons.append(bat) |
437 base_text, new_text, action = file_data[path] | 450 base_text, new_text, action = file_data[path] |
438 compute_delta = True | 451 compute_delta = True |
439 if action == 'modify': | 452 if action == 'modify': |
440 baton = editor.open_file(path, parent, base_revision, pool) | 453 baton = editor.open_file(path, parent, base_revision, pool) |
441 elif action == 'add': | 454 elif action == 'add': |
442 try: | 455 frompath, fromrev = copies.get(path, (None, -1)) |
443 frompath, fromrev = copies.get(path, (None, -1)) | 456 if frompath: |
444 if frompath: | 457 frompath = self.svn_url + '/' + frompath |
445 frompath = self.svn_url + '/' + frompath | 458 baton = editor.add_file(path, parent, frompath, fromrev, pool) |
446 baton = editor.add_file(path, parent, frompath, fromrev, pool) | |
447 except (core.SubversionException, TypeError), e: #pragma: no cover | |
448 print e | |
449 raise | |
450 elif action == 'delete': | 459 elif action == 'delete': |
451 baton = editor.delete_entry(path, base_revision, parent, pool) | 460 baton = editor.delete_entry(path, base_revision, parent, pool) |
452 compute_delta = False | 461 compute_delta = False |
453 | 462 |
454 if path in properties: | 463 if path in properties: |
473 delta.path_driver(editor, edit_baton, base_revision, paths, driver_cb, | 482 delta.path_driver(editor, edit_baton, base_revision, paths, driver_cb, |
474 self.pool) | 483 self.pool) |
475 editor.close_edit(edit_baton, self.pool) | 484 editor.close_edit(edit_baton, self.pool) |
476 | 485 |
477 def get_replay(self, revision, editor, oldest_rev_i_have=0): | 486 def get_replay(self, revision, editor, oldest_rev_i_have=0): |
487 # this method has a tendency to chew through RAM if you don't re-init | |
488 self.init_ra_and_client() | |
478 e_ptr, e_baton = delta.make_editor(editor) | 489 e_ptr, e_baton = delta.make_editor(editor) |
479 try: | 490 try: |
480 ra.replay(self.ra, revision, oldest_rev_i_have, True, e_ptr, | 491 ra.replay(self.ra, revision, oldest_rev_i_have, True, e_ptr, |
481 e_baton, self.pool) | 492 e_baton, self.pool) |
482 except core.SubversionException, e: #pragma: no cover | 493 except core.SubversionException, e: #pragma: no cover |
494 # can I depend on this number being constant? | |
483 if (e.apr_err == core.SVN_ERR_RA_NOT_IMPLEMENTED or | 495 if (e.apr_err == core.SVN_ERR_RA_NOT_IMPLEMENTED or |
484 e.apr_err == core.SVN_ERR_UNSUPPORTED_FEATURE): | 496 e.apr_err == core.SVN_ERR_UNSUPPORTED_FEATURE): |
485 raise SubversionRepoCanNotReplay, ('This Subversion server ' | 497 raise SubversionRepoCanNotReplay, ('This Subversion server ' |
486 'is older than 1.4.0, and cannot satisfy replay requests.') | 498 'is older than 1.4.0, and cannot satisfy replay requests.') |
487 else: | 499 else: |
491 deleted=True, ignore_type=False): | 503 deleted=True, ignore_type=False): |
492 """Gets a unidiff of path at revision against revision-1. | 504 """Gets a unidiff of path at revision against revision-1. |
493 """ | 505 """ |
494 if not self.hasdiff3: | 506 if not self.hasdiff3: |
495 raise SubversionRepoCanNotDiff() | 507 raise SubversionRepoCanNotDiff() |
508 # works around an svn server keeping too many open files (observed | |
509 # in an svnserve from the 1.2 era) | |
510 self.init_ra_and_client() | |
496 | 511 |
497 assert path[0] != '/' | 512 assert path[0] != '/' |
498 url = self.svn_url + '/' + path | 513 url = self.svn_url + '/' + path |
499 url2 = url | 514 url2 = url |
500 if other_path is not None: | 515 if other_path is not None: |
554 mode = ("svn:special" in info) and 'l' or mode | 569 mode = ("svn:special" in info) and 'l' or mode |
555 except core.SubversionException, e: | 570 except core.SubversionException, e: |
556 notfound = (core.SVN_ERR_FS_NOT_FOUND, | 571 notfound = (core.SVN_ERR_FS_NOT_FOUND, |
557 core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) | 572 core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) |
558 if e.apr_err in notfound: # File not found | 573 if e.apr_err in notfound: # File not found |
559 raise IOError, e.args[0] | 574 raise IOError() |
560 raise | 575 raise |
561 if mode == 'l': | 576 if mode == 'l': |
562 linkprefix = "link " | 577 linkprefix = "link " |
563 if data.startswith(linkprefix): | 578 if data.startswith(linkprefix): |
564 data = data[len(linkprefix):] | 579 data = data[len(linkprefix):] |
596 'dirpath' and 'kind' is 'f' if the entry is a file, 'd' if it is a | 611 'dirpath' and 'kind' is 'f' if the entry is a file, 'd' if it is a |
597 directory. Raise IOError if the directory cannot be found at given | 612 directory. Raise IOError if the directory cannot be found at given |
598 revision. | 613 revision. |
599 """ | 614 """ |
600 dirpath = dirpath.strip('/') | 615 dirpath = dirpath.strip('/') |
616 pool = core.Pool() | |
601 rpath = '/'.join([self.svn_url, dirpath]).strip('/') | 617 rpath = '/'.join([self.svn_url, dirpath]).strip('/') |
602 rev = optrev(revision) | 618 rev = optrev(revision) |
603 try: | 619 try: |
604 entries = client.ls(rpath, rev, True, self.client_context, | 620 entries = client.ls(rpath, rev, True, self.client_context, pool) |
605 self.pool) | |
606 except core.SubversionException, e: | 621 except core.SubversionException, e: |
607 if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: | 622 if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: |
608 raise IOError('%s cannot be found at r%d' % (dirpath, revision)) | 623 raise IOError('%s cannot be found at r%d' % (dirpath, revision)) |
609 raise | 624 raise |
610 for path, e in entries.iteritems(): | 625 for path, e in entries.iteritems(): |