diff options
Diffstat (limited to 'git_config.py')
-rw-r--r-- | git_config.py | 426 |
1 files changed, 227 insertions, 199 deletions
diff --git a/git_config.py b/git_config.py index 8de3200c..3cd09391 100644 --- a/git_config.py +++ b/git_config.py | |||
@@ -1,5 +1,3 @@ | |||
1 | # -*- coding:utf-8 -*- | ||
2 | # | ||
3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
4 | # | 2 | # |
5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
@@ -14,84 +12,83 @@ | |||
14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
15 | # limitations under the License. | 13 | # limitations under the License. |
16 | 14 | ||
17 | from __future__ import print_function | ||
18 | |||
19 | import contextlib | 15 | import contextlib |
16 | import datetime | ||
20 | import errno | 17 | import errno |
18 | from http.client import HTTPException | ||
21 | import json | 19 | import json |
22 | import os | 20 | import os |
23 | import re | 21 | import re |
24 | import ssl | 22 | import ssl |
25 | import subprocess | 23 | import subprocess |
26 | import sys | 24 | import sys |
27 | try: | 25 | import urllib.error |
28 | import threading as _threading | 26 | import urllib.request |
29 | except ImportError: | 27 | |
30 | import dummy_threading as _threading | ||
31 | import time | ||
32 | |||
33 | from pyversion import is_python3 | ||
34 | if is_python3(): | ||
35 | import urllib.request | ||
36 | import urllib.error | ||
37 | else: | ||
38 | import urllib2 | ||
39 | import imp | ||
40 | urllib = imp.new_module('urllib') | ||
41 | urllib.request = urllib2 | ||
42 | urllib.error = urllib2 | ||
43 | |||
44 | from signal import SIGTERM | ||
45 | from error import GitError, UploadError | 28 | from error import GitError, UploadError |
46 | import platform_utils | 29 | import platform_utils |
47 | from repo_trace import Trace | 30 | from repo_trace import Trace |
48 | if is_python3(): | ||
49 | from http.client import HTTPException | ||
50 | else: | ||
51 | from httplib import HTTPException | ||
52 | |||
53 | from git_command import GitCommand | 31 | from git_command import GitCommand |
54 | from git_command import ssh_sock | ||
55 | from git_command import terminate_ssh_clients | ||
56 | from git_refs import R_CHANGES, R_HEADS, R_TAGS | 32 | from git_refs import R_CHANGES, R_HEADS, R_TAGS |
57 | 33 | ||
34 | # Prefix that is prepended to all the keys of SyncAnalysisState's data | ||
35 | # that is saved in the config. | ||
36 | SYNC_STATE_PREFIX = 'repo.syncstate.' | ||
37 | |||
58 | ID_RE = re.compile(r'^[0-9a-f]{40}$') | 38 | ID_RE = re.compile(r'^[0-9a-f]{40}$') |
59 | 39 | ||
60 | REVIEW_CACHE = dict() | 40 | REVIEW_CACHE = dict() |
61 | 41 | ||
42 | |||
62 | def IsChange(rev): | 43 | def IsChange(rev): |
63 | return rev.startswith(R_CHANGES) | 44 | return rev.startswith(R_CHANGES) |
64 | 45 | ||
46 | |||
65 | def IsId(rev): | 47 | def IsId(rev): |
66 | return ID_RE.match(rev) | 48 | return ID_RE.match(rev) |
67 | 49 | ||
50 | |||
68 | def IsTag(rev): | 51 | def IsTag(rev): |
69 | return rev.startswith(R_TAGS) | 52 | return rev.startswith(R_TAGS) |
70 | 53 | ||
54 | |||
71 | def IsImmutable(rev): | 55 | def IsImmutable(rev): |
72 | return IsChange(rev) or IsId(rev) or IsTag(rev) | 56 | return IsChange(rev) or IsId(rev) or IsTag(rev) |
73 | 57 | ||
58 | |||
74 | def _key(name): | 59 | def _key(name): |
75 | parts = name.split('.') | 60 | parts = name.split('.') |
76 | if len(parts) < 2: | 61 | if len(parts) < 2: |
77 | return name.lower() | 62 | return name.lower() |
78 | parts[ 0] = parts[ 0].lower() | 63 | parts[0] = parts[0].lower() |
79 | parts[-1] = parts[-1].lower() | 64 | parts[-1] = parts[-1].lower() |
80 | return '.'.join(parts) | 65 | return '.'.join(parts) |
81 | 66 | ||
67 | |||
82 | class GitConfig(object): | 68 | class GitConfig(object): |
83 | _ForUser = None | 69 | _ForUser = None |
84 | 70 | ||
71 | _USER_CONFIG = '~/.gitconfig' | ||
72 | |||
73 | _ForSystem = None | ||
74 | _SYSTEM_CONFIG = '/etc/gitconfig' | ||
75 | |||
76 | @classmethod | ||
77 | def ForSystem(cls): | ||
78 | if cls._ForSystem is None: | ||
79 | cls._ForSystem = cls(configfile=cls._SYSTEM_CONFIG) | ||
80 | return cls._ForSystem | ||
81 | |||
85 | @classmethod | 82 | @classmethod |
86 | def ForUser(cls): | 83 | def ForUser(cls): |
87 | if cls._ForUser is None: | 84 | if cls._ForUser is None: |
88 | cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig')) | 85 | cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG)) |
89 | return cls._ForUser | 86 | return cls._ForUser |
90 | 87 | ||
91 | @classmethod | 88 | @classmethod |
92 | def ForRepository(cls, gitdir, defaults=None): | 89 | def ForRepository(cls, gitdir, defaults=None): |
93 | return cls(configfile = os.path.join(gitdir, 'config'), | 90 | return cls(configfile=os.path.join(gitdir, 'config'), |
94 | defaults = defaults) | 91 | defaults=defaults) |
95 | 92 | ||
96 | def __init__(self, configfile, defaults=None, jsonFile=None): | 93 | def __init__(self, configfile, defaults=None, jsonFile=None): |
97 | self.file = configfile | 94 | self.file = configfile |
@@ -104,18 +101,74 @@ class GitConfig(object): | |||
104 | self._json = jsonFile | 101 | self._json = jsonFile |
105 | if self._json is None: | 102 | if self._json is None: |
106 | self._json = os.path.join( | 103 | self._json = os.path.join( |
107 | os.path.dirname(self.file), | 104 | os.path.dirname(self.file), |
108 | '.repo_' + os.path.basename(self.file) + '.json') | 105 | '.repo_' + os.path.basename(self.file) + '.json') |
106 | |||
107 | def ClearCache(self): | ||
108 | """Clear the in-memory cache of config.""" | ||
109 | self._cache_dict = None | ||
109 | 110 | ||
110 | def Has(self, name, include_defaults = True): | 111 | def Has(self, name, include_defaults=True): |
111 | """Return true if this configuration file has the key. | 112 | """Return true if this configuration file has the key. |
112 | """ | 113 | """ |
113 | if _key(name) in self._cache: | 114 | if _key(name) in self._cache: |
114 | return True | 115 | return True |
115 | if include_defaults and self.defaults: | 116 | if include_defaults and self.defaults: |
116 | return self.defaults.Has(name, include_defaults = True) | 117 | return self.defaults.Has(name, include_defaults=True) |
117 | return False | 118 | return False |
118 | 119 | ||
120 | def GetInt(self, name): | ||
121 | """Returns an integer from the configuration file. | ||
122 | |||
123 | This follows the git config syntax. | ||
124 | |||
125 | Args: | ||
126 | name: The key to lookup. | ||
127 | |||
128 | Returns: | ||
129 | None if the value was not defined, or is not a boolean. | ||
130 | Otherwise, the number itself. | ||
131 | """ | ||
132 | v = self.GetString(name) | ||
133 | if v is None: | ||
134 | return None | ||
135 | v = v.strip() | ||
136 | |||
137 | mult = 1 | ||
138 | if v.endswith('k'): | ||
139 | v = v[:-1] | ||
140 | mult = 1024 | ||
141 | elif v.endswith('m'): | ||
142 | v = v[:-1] | ||
143 | mult = 1024 * 1024 | ||
144 | elif v.endswith('g'): | ||
145 | v = v[:-1] | ||
146 | mult = 1024 * 1024 * 1024 | ||
147 | |||
148 | base = 10 | ||
149 | if v.startswith('0x'): | ||
150 | base = 16 | ||
151 | |||
152 | try: | ||
153 | return int(v, base=base) * mult | ||
154 | except ValueError: | ||
155 | return None | ||
156 | |||
157 | def DumpConfigDict(self): | ||
158 | """Returns the current configuration dict. | ||
159 | |||
160 | Configuration data is information only (e.g. logging) and | ||
161 | should not be considered a stable data-source. | ||
162 | |||
163 | Returns: | ||
164 | dict of {<key>, <value>} for git configuration cache. | ||
165 | <value> are strings converted by GetString. | ||
166 | """ | ||
167 | config_dict = {} | ||
168 | for key in self._cache: | ||
169 | config_dict[key] = self.GetString(key) | ||
170 | return config_dict | ||
171 | |||
119 | def GetBoolean(self, name): | 172 | def GetBoolean(self, name): |
120 | """Returns a boolean from the configuration file. | 173 | """Returns a boolean from the configuration file. |
121 | None : The value was not defined, or is not a boolean. | 174 | None : The value was not defined, or is not a boolean. |
@@ -132,6 +185,12 @@ class GitConfig(object): | |||
132 | return False | 185 | return False |
133 | return None | 186 | return None |
134 | 187 | ||
188 | def SetBoolean(self, name, value): | ||
189 | """Set the truthy value for a key.""" | ||
190 | if value is not None: | ||
191 | value = 'true' if value else 'false' | ||
192 | self.SetString(name, value) | ||
193 | |||
135 | def GetString(self, name, all_keys=False): | 194 | def GetString(self, name, all_keys=False): |
136 | """Get the first value for a key, or None if it is not defined. | 195 | """Get the first value for a key, or None if it is not defined. |
137 | 196 | ||
@@ -142,7 +201,7 @@ class GitConfig(object): | |||
142 | v = self._cache[_key(name)] | 201 | v = self._cache[_key(name)] |
143 | except KeyError: | 202 | except KeyError: |
144 | if self.defaults: | 203 | if self.defaults: |
145 | return self.defaults.GetString(name, all_keys = all_keys) | 204 | return self.defaults.GetString(name, all_keys=all_keys) |
146 | v = [] | 205 | v = [] |
147 | 206 | ||
148 | if not all_keys: | 207 | if not all_keys: |
@@ -153,7 +212,7 @@ class GitConfig(object): | |||
153 | r = [] | 212 | r = [] |
154 | r.extend(v) | 213 | r.extend(v) |
155 | if self.defaults: | 214 | if self.defaults: |
156 | r.extend(self.defaults.GetString(name, all_keys = True)) | 215 | r.extend(self.defaults.GetString(name, all_keys=True)) |
157 | return r | 216 | return r |
158 | 217 | ||
159 | def SetString(self, name, value): | 218 | def SetString(self, name, value): |
@@ -212,12 +271,28 @@ class GitConfig(object): | |||
212 | self._branches[b.name] = b | 271 | self._branches[b.name] = b |
213 | return b | 272 | return b |
214 | 273 | ||
274 | def GetSyncAnalysisStateData(self): | ||
275 | """Returns data to be logged for the analysis of sync performance.""" | ||
276 | return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)} | ||
277 | |||
278 | def UpdateSyncAnalysisState(self, options, superproject_logging_data): | ||
279 | """Update Config's SYNC_STATE_PREFIX* data with the latest sync data. | ||
280 | |||
281 | Args: | ||
282 | options: Options passed to sync returned from optparse. See _Options(). | ||
283 | superproject_logging_data: A dictionary of superproject data that is to be logged. | ||
284 | |||
285 | Returns: | ||
286 | SyncAnalysisState object. | ||
287 | """ | ||
288 | return SyncAnalysisState(self, options, superproject_logging_data) | ||
289 | |||
215 | def GetSubSections(self, section): | 290 | def GetSubSections(self, section): |
216 | """List all subsection names matching $section.*.* | 291 | """List all subsection names matching $section.*.* |
217 | """ | 292 | """ |
218 | return self._sections.get(section, set()) | 293 | return self._sections.get(section, set()) |
219 | 294 | ||
220 | def HasSection(self, section, subsection = ''): | 295 | def HasSection(self, section, subsection=''): |
221 | """Does at least one key in section.subsection exist? | 296 | """Does at least one key in section.subsection exist? |
222 | """ | 297 | """ |
223 | try: | 298 | try: |
@@ -268,8 +343,7 @@ class GitConfig(object): | |||
268 | 343 | ||
269 | def _ReadJson(self): | 344 | def _ReadJson(self): |
270 | try: | 345 | try: |
271 | if os.path.getmtime(self._json) \ | 346 | if os.path.getmtime(self._json) <= os.path.getmtime(self.file): |
272 | <= os.path.getmtime(self.file): | ||
273 | platform_utils.remove(self._json) | 347 | platform_utils.remove(self._json) |
274 | return None | 348 | return None |
275 | except OSError: | 349 | except OSError: |
@@ -278,8 +352,8 @@ class GitConfig(object): | |||
278 | Trace(': parsing %s', self.file) | 352 | Trace(': parsing %s', self.file) |
279 | with open(self._json) as fd: | 353 | with open(self._json) as fd: |
280 | return json.load(fd) | 354 | return json.load(fd) |
281 | except (IOError, ValueError): | 355 | except (IOError, ValueErrorl): |
282 | platform_utils.remove(self._json) | 356 | platform_utils.remove(self._json, missing_ok=True) |
283 | return None | 357 | return None |
284 | 358 | ||
285 | def _SaveJson(self, cache): | 359 | def _SaveJson(self, cache): |
@@ -287,8 +361,7 @@ class GitConfig(object): | |||
287 | with open(self._json, 'w') as fd: | 361 | with open(self._json, 'w') as fd: |
288 | json.dump(cache, fd, indent=2) | 362 | json.dump(cache, fd, indent=2) |
289 | except (IOError, TypeError): | 363 | except (IOError, TypeError): |
290 | if os.path.exists(self._json): | 364 | platform_utils.remove(self._json, missing_ok=True) |
291 | platform_utils.remove(self._json) | ||
292 | 365 | ||
293 | def _ReadGit(self): | 366 | def _ReadGit(self): |
294 | """ | 367 | """ |
@@ -298,11 +371,10 @@ class GitConfig(object): | |||
298 | 371 | ||
299 | """ | 372 | """ |
300 | c = {} | 373 | c = {} |
301 | d = self._do('--null', '--list') | 374 | if not os.path.exists(self.file): |
302 | if d is None: | ||
303 | return c | 375 | return c |
304 | if not is_python3(): | 376 | |
305 | d = d.decode('utf-8') | 377 | d = self._do('--null', '--list') |
306 | for line in d.rstrip('\0').split('\0'): | 378 | for line in d.rstrip('\0').split('\0'): |
307 | if '\n' in line: | 379 | if '\n' in line: |
308 | key, val = line.split('\n', 1) | 380 | key, val = line.split('\n', 1) |
@@ -318,17 +390,26 @@ class GitConfig(object): | |||
318 | return c | 390 | return c |
319 | 391 | ||
320 | def _do(self, *args): | 392 | def _do(self, *args): |
321 | command = ['config', '--file', self.file] | 393 | if self.file == self._SYSTEM_CONFIG: |
394 | command = ['config', '--system', '--includes'] | ||
395 | else: | ||
396 | command = ['config', '--file', self.file, '--includes'] | ||
322 | command.extend(args) | 397 | command.extend(args) |
323 | 398 | ||
324 | p = GitCommand(None, | 399 | p = GitCommand(None, |
325 | command, | 400 | command, |
326 | capture_stdout = True, | 401 | capture_stdout=True, |
327 | capture_stderr = True) | 402 | capture_stderr=True) |
328 | if p.Wait() == 0: | 403 | if p.Wait() == 0: |
329 | return p.stdout | 404 | return p.stdout |
330 | else: | 405 | else: |
331 | GitError('git config %s: %s' % (str(args), p.stderr)) | 406 | raise GitError('git config %s: %s' % (str(args), p.stderr)) |
407 | |||
408 | |||
409 | class RepoConfig(GitConfig): | ||
410 | """User settings for repo itself.""" | ||
411 | |||
412 | _USER_CONFIG = '~/.repoconfig/config' | ||
332 | 413 | ||
333 | 414 | ||
334 | class RefSpec(object): | 415 | class RefSpec(object): |
@@ -387,133 +468,16 @@ class RefSpec(object): | |||
387 | return s | 468 | return s |
388 | 469 | ||
389 | 470 | ||
390 | _master_processes = [] | ||
391 | _master_keys = set() | ||
392 | _ssh_master = True | ||
393 | _master_keys_lock = None | ||
394 | |||
395 | def init_ssh(): | ||
396 | """Should be called once at the start of repo to init ssh master handling. | ||
397 | |||
398 | At the moment, all we do is to create our lock. | ||
399 | """ | ||
400 | global _master_keys_lock | ||
401 | assert _master_keys_lock is None, "Should only call init_ssh once" | ||
402 | _master_keys_lock = _threading.Lock() | ||
403 | |||
404 | def _open_ssh(host, port=None): | ||
405 | global _ssh_master | ||
406 | |||
407 | # Acquire the lock. This is needed to prevent opening multiple masters for | ||
408 | # the same host when we're running "repo sync -jN" (for N > 1) _and_ the | ||
409 | # manifest <remote fetch="ssh://xyz"> specifies a different host from the | ||
410 | # one that was passed to repo init. | ||
411 | _master_keys_lock.acquire() | ||
412 | try: | ||
413 | |||
414 | # Check to see whether we already think that the master is running; if we | ||
415 | # think it's already running, return right away. | ||
416 | if port is not None: | ||
417 | key = '%s:%s' % (host, port) | ||
418 | else: | ||
419 | key = host | ||
420 | |||
421 | if key in _master_keys: | ||
422 | return True | ||
423 | |||
424 | if not _ssh_master \ | ||
425 | or 'GIT_SSH' in os.environ \ | ||
426 | or sys.platform in ('win32', 'cygwin'): | ||
427 | # failed earlier, or cygwin ssh can't do this | ||
428 | # | ||
429 | return False | ||
430 | |||
431 | # We will make two calls to ssh; this is the common part of both calls. | ||
432 | command_base = ['ssh', | ||
433 | '-o','ControlPath %s' % ssh_sock(), | ||
434 | host] | ||
435 | if port is not None: | ||
436 | command_base[1:1] = ['-p', str(port)] | ||
437 | |||
438 | # Since the key wasn't in _master_keys, we think that master isn't running. | ||
439 | # ...but before actually starting a master, we'll double-check. This can | ||
440 | # be important because we can't tell that that 'git@myhost.com' is the same | ||
441 | # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file. | ||
442 | check_command = command_base + ['-O','check'] | ||
443 | try: | ||
444 | Trace(': %s', ' '.join(check_command)) | ||
445 | check_process = subprocess.Popen(check_command, | ||
446 | stdout=subprocess.PIPE, | ||
447 | stderr=subprocess.PIPE) | ||
448 | check_process.communicate() # read output, but ignore it... | ||
449 | isnt_running = check_process.wait() | ||
450 | |||
451 | if not isnt_running: | ||
452 | # Our double-check found that the master _was_ infact running. Add to | ||
453 | # the list of keys. | ||
454 | _master_keys.add(key) | ||
455 | return True | ||
456 | except Exception: | ||
457 | # Ignore excpetions. We we will fall back to the normal command and print | ||
458 | # to the log there. | ||
459 | pass | ||
460 | |||
461 | command = command_base[:1] + \ | ||
462 | ['-M', '-N'] + \ | ||
463 | command_base[1:] | ||
464 | try: | ||
465 | Trace(': %s', ' '.join(command)) | ||
466 | p = subprocess.Popen(command) | ||
467 | except Exception as e: | ||
468 | _ssh_master = False | ||
469 | print('\nwarn: cannot enable ssh control master for %s:%s\n%s' | ||
470 | % (host,port, str(e)), file=sys.stderr) | ||
471 | return False | ||
472 | |||
473 | time.sleep(1) | ||
474 | ssh_died = (p.poll() is not None) | ||
475 | if ssh_died: | ||
476 | return False | ||
477 | |||
478 | _master_processes.append(p) | ||
479 | _master_keys.add(key) | ||
480 | return True | ||
481 | finally: | ||
482 | _master_keys_lock.release() | ||
483 | |||
484 | def close_ssh(): | ||
485 | global _master_keys_lock | ||
486 | |||
487 | terminate_ssh_clients() | ||
488 | |||
489 | for p in _master_processes: | ||
490 | try: | ||
491 | os.kill(p.pid, SIGTERM) | ||
492 | p.wait() | ||
493 | except OSError: | ||
494 | pass | ||
495 | del _master_processes[:] | ||
496 | _master_keys.clear() | ||
497 | |||
498 | d = ssh_sock(create=False) | ||
499 | if d: | ||
500 | try: | ||
501 | platform_utils.rmdir(os.path.dirname(d)) | ||
502 | except OSError: | ||
503 | pass | ||
504 | |||
505 | # We're done with the lock, so we can delete it. | ||
506 | _master_keys_lock = None | ||
507 | |||
508 | URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') | ||
509 | URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/') | 471 | URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/') |
510 | 472 | ||
473 | |||
511 | def GetSchemeFromUrl(url): | 474 | def GetSchemeFromUrl(url): |
512 | m = URI_ALL.match(url) | 475 | m = URI_ALL.match(url) |
513 | if m: | 476 | if m: |
514 | return m.group(1) | 477 | return m.group(1) |
515 | return None | 478 | return None |
516 | 479 | ||
480 | |||
517 | @contextlib.contextmanager | 481 | @contextlib.contextmanager |
518 | def GetUrlCookieFile(url, quiet): | 482 | def GetUrlCookieFile(url, quiet): |
519 | if url.startswith('persistent-'): | 483 | if url.startswith('persistent-'): |
@@ -554,29 +518,11 @@ def GetUrlCookieFile(url, quiet): | |||
554 | cookiefile = os.path.expanduser(cookiefile) | 518 | cookiefile = os.path.expanduser(cookiefile) |
555 | yield cookiefile, None | 519 | yield cookiefile, None |
556 | 520 | ||
557 | def _preconnect(url): | ||
558 | m = URI_ALL.match(url) | ||
559 | if m: | ||
560 | scheme = m.group(1) | ||
561 | host = m.group(2) | ||
562 | if ':' in host: | ||
563 | host, port = host.split(':') | ||
564 | else: | ||
565 | port = None | ||
566 | if scheme in ('ssh', 'git+ssh', 'ssh+git'): | ||
567 | return _open_ssh(host, port) | ||
568 | return False | ||
569 | |||
570 | m = URI_SCP.match(url) | ||
571 | if m: | ||
572 | host = m.group(1) | ||
573 | return _open_ssh(host) | ||
574 | |||
575 | return False | ||
576 | 521 | ||
577 | class Remote(object): | 522 | class Remote(object): |
578 | """Configuration options related to a remote. | 523 | """Configuration options related to a remote. |
579 | """ | 524 | """ |
525 | |||
580 | def __init__(self, config, name): | 526 | def __init__(self, config, name): |
581 | self._config = config | 527 | self._config = config |
582 | self.name = name | 528 | self.name = name |
@@ -585,7 +531,7 @@ class Remote(object): | |||
585 | self.review = self._Get('review') | 531 | self.review = self._Get('review') |
586 | self.projectname = self._Get('projectname') | 532 | self.projectname = self._Get('projectname') |
587 | self.fetch = list(map(RefSpec.FromString, | 533 | self.fetch = list(map(RefSpec.FromString, |
588 | self._Get('fetch', all_keys=True))) | 534 | self._Get('fetch', all_keys=True))) |
589 | self._review_url = None | 535 | self._review_url = None |
590 | 536 | ||
591 | def _InsteadOf(self): | 537 | def _InsteadOf(self): |
@@ -599,8 +545,8 @@ class Remote(object): | |||
599 | insteadOfList = globCfg.GetString(key, all_keys=True) | 545 | insteadOfList = globCfg.GetString(key, all_keys=True) |
600 | 546 | ||
601 | for insteadOf in insteadOfList: | 547 | for insteadOf in insteadOfList: |
602 | if self.url.startswith(insteadOf) \ | 548 | if (self.url.startswith(insteadOf) |
603 | and len(insteadOf) > len(longest): | 549 | and len(insteadOf) > len(longest)): |
604 | longest = insteadOf | 550 | longest = insteadOf |
605 | longestUrl = url | 551 | longestUrl = url |
606 | 552 | ||
@@ -609,9 +555,23 @@ class Remote(object): | |||
609 | 555 | ||
610 | return self.url.replace(longest, longestUrl, 1) | 556 | return self.url.replace(longest, longestUrl, 1) |
611 | 557 | ||
612 | def PreConnectFetch(self): | 558 | def PreConnectFetch(self, ssh_proxy): |
559 | """Run any setup for this remote before we connect to it. | ||
560 | |||
561 | In practice, if the remote is using SSH, we'll attempt to create a new | ||
562 | SSH master session to it for reuse across projects. | ||
563 | |||
564 | Args: | ||
565 | ssh_proxy: The SSH settings for managing master sessions. | ||
566 | |||
567 | Returns: | ||
568 | Whether the preconnect phase for this remote was successful. | ||
569 | """ | ||
570 | if not ssh_proxy: | ||
571 | return True | ||
572 | |||
613 | connectionUrl = self._InsteadOf() | 573 | connectionUrl = self._InsteadOf() |
614 | return _preconnect(connectionUrl) | 574 | return ssh_proxy.preconnect(connectionUrl) |
615 | 575 | ||
616 | def ReviewUrl(self, userEmail, validate_certs): | 576 | def ReviewUrl(self, userEmail, validate_certs): |
617 | if self._review_url is None: | 577 | if self._review_url is None: |
@@ -731,12 +691,13 @@ class Remote(object): | |||
731 | 691 | ||
732 | def _Get(self, key, all_keys=False): | 692 | def _Get(self, key, all_keys=False): |
733 | key = 'remote.%s.%s' % (self.name, key) | 693 | key = 'remote.%s.%s' % (self.name, key) |
734 | return self._config.GetString(key, all_keys = all_keys) | 694 | return self._config.GetString(key, all_keys=all_keys) |
735 | 695 | ||
736 | 696 | ||
737 | class Branch(object): | 697 | class Branch(object): |
738 | """Configuration options related to a single branch. | 698 | """Configuration options related to a single branch. |
739 | """ | 699 | """ |
700 | |||
740 | def __init__(self, config, name): | 701 | def __init__(self, config, name): |
741 | self._config = config | 702 | self._config = config |
742 | self.name = name | 703 | self.name = name |
@@ -780,4 +741,71 @@ class Branch(object): | |||
780 | 741 | ||
781 | def _Get(self, key, all_keys=False): | 742 | def _Get(self, key, all_keys=False): |
782 | key = 'branch.%s.%s' % (self.name, key) | 743 | key = 'branch.%s.%s' % (self.name, key) |
783 | return self._config.GetString(key, all_keys = all_keys) | 744 | return self._config.GetString(key, all_keys=all_keys) |
745 | |||
746 | |||
747 | class SyncAnalysisState: | ||
748 | """Configuration options related to logging of sync state for analysis. | ||
749 | |||
750 | This object is versioned. | ||
751 | """ | ||
752 | def __init__(self, config, options, superproject_logging_data): | ||
753 | """Initializes SyncAnalysisState. | ||
754 | |||
755 | Saves the following data into the |config| object. | ||
756 | - sys.argv, options, superproject's logging data. | ||
757 | - repo.*, branch.* and remote.* parameters from config object. | ||
758 | - Current time as synctime. | ||
759 | - Version number of the object. | ||
760 | |||
761 | All the keys saved by this object are prepended with SYNC_STATE_PREFIX. | ||
762 | |||
763 | Args: | ||
764 | config: GitConfig object to store all options. | ||
765 | options: Options passed to sync returned from optparse. See _Options(). | ||
766 | superproject_logging_data: A dictionary of superproject data that is to be logged. | ||
767 | """ | ||
768 | self._config = config | ||
769 | now = datetime.datetime.utcnow() | ||
770 | self._Set('main.synctime', now.isoformat() + 'Z') | ||
771 | self._Set('main.version', '1') | ||
772 | self._Set('sys.argv', sys.argv) | ||
773 | for key, value in superproject_logging_data.items(): | ||
774 | self._Set(f'superproject.{key}', value) | ||
775 | for key, value in options.__dict__.items(): | ||
776 | self._Set(f'options.{key}', value) | ||
777 | config_items = config.DumpConfigDict().items() | ||
778 | EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'} | ||
779 | self._SetDictionary({k: v for k, v in config_items | ||
780 | if not k.startswith(SYNC_STATE_PREFIX) and | ||
781 | k.split('.', 1)[0] in EXTRACT_NAMESPACES}) | ||
782 | |||
783 | def _SetDictionary(self, data): | ||
784 | """Save all key/value pairs of |data| dictionary. | ||
785 | |||
786 | Args: | ||
787 | data: A dictionary whose key/value are to be saved. | ||
788 | """ | ||
789 | for key, value in data.items(): | ||
790 | self._Set(key, value) | ||
791 | |||
792 | def _Set(self, key, value): | ||
793 | """Set the |value| for a |key| in the |_config| member. | ||
794 | |||
795 | |key| is prepended with the value of SYNC_STATE_PREFIX constant. | ||
796 | |||
797 | Args: | ||
798 | key: Name of the key. | ||
799 | value: |value| could be of any type. If it is 'bool', it will be saved | ||
800 | as a Boolean and for all other types, it will be saved as a String. | ||
801 | """ | ||
802 | if value is None: | ||
803 | return | ||
804 | sync_key = f'{SYNC_STATE_PREFIX}{key}' | ||
805 | sync_key = sync_key.replace('_', '') | ||
806 | if isinstance(value, str): | ||
807 | self._config.SetString(sync_key, value) | ||
808 | elif isinstance(value, bool): | ||
809 | self._config.SetBoolean(sync_key, value) | ||
810 | else: | ||
811 | self._config.SetString(sync_key, str(value)) | ||