Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import collections | |
| 6 import logging | |
| 7 import posixpath | |
| 8 import sys | |
| 9 | |
| 10 from infra.libs.git2 import CalledProcessError | |
| 11 from infra.libs.git2 import INVALID | |
| 12 from infra.libs.git2 import config_ref | |
| 13 from infra.libs.git2 import repo | |
| 14 | |
| 15 | |
| 16 LOGGER = logging.getLogger(__name__) | |
| 17 | |
| 18 | |
| 19 ################################################################################ | |
| 20 # ConfigRef | |
| 21 ################################################################################ | |
| 22 | |
| 23 class GsubtreedConfigRef(config_ref.ConfigRef): | |
| 24 CONVERT = { | |
| 25 'interval': lambda self, val: float(val), | |
| 26 'subtree_synthesized_prefix': lambda self, val: str(val), | |
| 27 'subtree_processed_prefix': lambda self, val: str(val), | |
| 28 | |
| 29 'base_url': lambda self, val: str(val) or self.repo.url, | |
| 30 'enabled_refglobs': lambda self, val: map(str, list(val)), | |
| 31 # normpath to avoid trailing/double-slash errors. | |
| 32 'enabled_paths': lambda self, val: map(posixpath.normpath, map(str, val)), | |
| 33 } | |
| 34 DEFAULTS = { | |
| 35 'interval': 5.0, | |
| 36 'subtree_processed_prefix': 'refs/subtree-processed', | |
| 37 'subtree_synthesized_prefix': 'refs/subtree-synthesized', | |
| 38 # e.g. while processing the subtree 'b/foo' on refs/heads/master | |
| 39 # refs/heads/master <- real commits | |
| 40 # refs/subtree-processed/b/foo/-/heads/master <- ancestor tag of master | |
|
Vadim Sh.
2014/08/17 19:37:59
Will this refs be created on a first use or some o
iannucci
2014/08/17 22:35:30
It'll do a push... normally that would create it,
| |
| 41 # refs/subtree-synthesized/b/foo/-/heads/master <- ref with synth commits | |
| 42 # For the sake of implementation simplicity, this daemon assumes the | |
| 43 # googlesource.com guarantee of transactional multi-ref pushes within a | |
| 44 # single repo. | |
| 45 | |
| 46 'base_url': None, | |
|
Vadim Sh.
2014/08/17 19:37:59
Document.
iannucci
2014/08/17 22:35:29
Done.
| |
| 47 'enabled_refglobs': ['refs/heads/*'], | |
| 48 'enabled_paths': [], | |
| 49 } | |
| 50 REF = 'refs/gsubtreed-config/main' | |
| 51 | |
| 52 | |
| 53 | |
| 54 ################################################################################ | |
| 55 # Core functionality | |
| 56 ################################################################################ | |
| 57 | |
| 58 def process_path(path, origin_repo, config, fake_base_repo_url=None): | |
| 59 def join(prefix, ref): | |
| 60 assert ref.ref.startswith('refs/') | |
| 61 ref = '/'.join((prefix, path)) + '/-/' + ref.ref[len('refs/'):] | |
| 62 return origin_repo[ref] | |
| 63 | |
| 64 origin_push = {} | |
| 65 | |
| 66 base_url = fake_base_repo_url or config['base_url'] | |
|
Vadim Sh.
2014/08/17 19:37:59
can't you mock config['base_url'] in tests to get
iannucci
2014/08/17 22:35:30
Did a thing. ptal.
| |
| 67 mirror_url = '[FAKE-BASE-URL]' if fake_base_repo_url else origin_repo.url | |
| 68 | |
| 69 subtree_repo = repo.Repo(posixpath.join(base_url, path)) | |
| 70 subtree_repo.repos_dir = origin_repo.repos_dir | |
| 71 subtree_repo.reify(share_from=origin_repo) | |
|
Vadim Sh.
2014/08/17 19:37:59
what if repo at base_url is not initially dervied
iannucci
2014/08/17 22:35:30
Yes, it's safe. Object sharing only means that it'
| |
| 72 subtree_repo.run('fetch', stdout=sys.stdout, stderr=sys.stderr) | |
| 73 subtree_repo_push = {} | |
| 74 | |
| 75 synthed_count = 0 | |
| 76 | |
| 77 for glob in config['enabled_refglobs']: | |
| 78 for ref in origin_repo.refglob(glob): | |
| 79 LOGGER.info('processing ref %s', ref) | |
| 80 processed = join(config['subtree_processed_prefix'], ref) | |
| 81 synthed = join(config['subtree_synthesized_prefix'], ref) | |
| 82 | |
| 83 synth_parent = synthed.commit | |
| 84 cur_tree = synthed.commit.data.tree | |
|
Vadim Sh.
2014/08/17 19:37:59
is it None if ref doesn't exist yet?
iannucci
2014/08/17 22:35:30
It's INVALID (and thus synthed.commit.data.tree is
| |
| 85 LOGGER.info('starting with tree %r', cur_tree) | |
| 86 | |
| 87 for commit in processed.to(ref): | |
| 88 LOGGER.info('processing commit %s', commit) | |
| 89 try: | |
| 90 obj_name = '{.hsh}:{}'.format(commit, path) | |
| 91 typ = origin_repo.run('cat-file', '-t', obj_name).strip() | |
| 92 if typ != 'tree': | |
| 93 LOGGER.warn('path %r is not a tree in commit %s', path, commit) | |
| 94 continue | |
| 95 dir_tree = origin_repo.run('rev-parse', obj_name).strip() | |
| 96 except CalledProcessError: | |
| 97 continue | |
| 98 | |
| 99 if dir_tree != cur_tree: | |
| 100 LOGGER.info('found new tree %r', dir_tree) | |
| 101 cur_tree = dir_tree | |
| 102 synthed_count += 1 | |
| 103 synth_parent = commit.alter( | |
| 104 parents=[synth_parent.hsh] if synth_parent is not INVALID else [], | |
| 105 tree=dir_tree, | |
| 106 footers=collections.OrderedDict([ | |
| 107 ('Cr-Mirrored-From', [mirror_url]), | |
| 108 ('Cr-Mirrored-Commit', [commit.hsh]), | |
|
Vadim Sh.
2014/08/17 19:37:59
It will also inherit Cr-Commit-Position, right?
iannucci
2014/08/17 22:35:30
Yes, this is just adding footers to whatever's the
| |
| 109 ('Cr-Mirrored-Subtree', [path]), | |
| 110 ]), | |
| 111 ) | |
| 112 origin_push[synthed] = synth_parent | |
| 113 subtree_repo_push[subtree_repo[ref.ref]] = synth_parent | |
| 114 | |
| 115 origin_push[processed] = ref.commit | |
| 116 | |
| 117 success = True | |
| 118 try: | |
| 119 # because the hashes are deterministic based on the real history, if the | |
| 120 # first push succeeds, but the second does not, it just means we'll end up | |
| 121 # doing a bit of extra work on the next loop, but correctness will still be | |
| 122 # ensured | |
| 123 subtree_repo.fast_forward_push(subtree_repo_push) | |
|
Vadim Sh.
2014/08/17 19:37:59
TODO: push to subtree repos in parallel. This can
iannucci
2014/08/17 22:35:29
Yeah I thought about it. I'll add a comment, but I
| |
| 124 origin_repo.fast_forward_push(origin_push) | |
| 125 except Exception: # pragma: no cover | |
| 126 LOGGER.exception('Caught exception while pushing in process_path') | |
| 127 success = False | |
| 128 | |
| 129 return success, synthed_count | |
| 130 | |
| 131 | |
| 132 def inner_loop(origin_repo, config, fake_base_repo_url=None): | |
| 133 """Returns (success, {path: #commits_synthesized}).""" | |
| 134 | |
| 135 LOGGER.debug('fetching %r', origin_repo) | |
| 136 origin_repo.run('fetch', stdout=sys.stdout, stderr=sys.stderr) | |
| 137 config.evaluate() | |
| 138 | |
| 139 success = True | |
| 140 processed = {} | |
| 141 for path in config['enabled_paths']: | |
| 142 LOGGER.info('processing path %s', path) | |
| 143 try: | |
| 144 path_success, num_synthed = process_path(path, origin_repo, config, | |
| 145 fake_base_repo_url) | |
| 146 success = path_success and success | |
| 147 processed[path] = num_synthed | |
| 148 except Exception: # pragma: no cover | |
| 149 LOGGER.exception('Caught in inner_loop') | |
| 150 success = False | |
| 151 | |
| 152 return success, processed | |
| OLD | NEW |