Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(380)

Unified Diff: infra/services/gsubtreed/gsubtreed.py

Issue 477623003: Add git subtree daemon service. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@add_dtree_support
Patch Set: address comments Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « infra/services/gsubtreed/__main__.py ('k') | infra/services/gsubtreed/test/__init__.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: infra/services/gsubtreed/gsubtreed.py
diff --git a/infra/services/gsubtreed/gsubtreed.py b/infra/services/gsubtreed/gsubtreed.py
new file mode 100644
index 0000000000000000000000000000000000000000..03b0d47b7873752227a9bef9d2c68ab85f623e1f
--- /dev/null
+++ b/infra/services/gsubtreed/gsubtreed.py
@@ -0,0 +1,164 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import collections
+import logging
+import posixpath
+import sys
+
+from infra.libs.git2 import CalledProcessError
+from infra.libs.git2 import INVALID
+from infra.libs.git2 import config_ref
+from infra.libs.git2 import repo
+
+
+LOGGER = logging.getLogger(__name__)
+
+
+################################################################################
+# ConfigRef
+################################################################################
+
+class GsubtreedConfigRef(config_ref.ConfigRef):
+ CONVERT = {
+ 'interval': lambda self, val: float(val),
+ 'subtree_synthesized_prefix': lambda self, val: str(val),
+ 'subtree_processed_prefix': lambda self, val: str(val),
+
+ 'base_url': lambda self, val: str(val) or self.repo.url,
+ 'enabled_refglobs': lambda self, val: map(str, list(val)),
+ # normpath to avoid trailing/double-slash errors.
+ 'enabled_paths': lambda self, val: map(posixpath.normpath, map(str, val)),
+ }
+ DEFAULTS = {
+ 'interval': 5.0,
+
+ # e.g. while processing the subtree 'b/foo' on refs/heads/master
+ # refs/heads/master <- real commits
+ # refs/subtree-processed/b/foo/-/heads/master <- ancestor tag of master
+ # refs/subtree-synthesized/b/foo/-/heads/master <- ref with synth commits
+ # For the sake of implementation simplicity, this daemon assumes the
+ # googlesource.com guarantee of transactional multi-ref pushes within a
+ # single repo.
+ 'subtree_processed_prefix': 'refs/subtree-processed',
+ 'subtree_synthesized_prefix': 'refs/subtree-synthesized',
+
+ # The base URL is the url relative to which all mirror repos are assumed to
+ # exist. For example, if you mirror the path 'bob', and base_url is
+ # https://host.domain.tld/main_repo, then it would assume that the mirror
+ # for the bob subtree is https://host.domain.tld/main_repo/bob.
+ #
+ # By default, base_url is set to the repo that gsubtreed is processing
+ 'base_url': None,
+ 'enabled_refglobs': ['refs/heads/*'],
+ 'enabled_paths': [],
+ }
+ REF = 'refs/gsubtreed-config/main'
+
+
+
+################################################################################
+# Core functionality
+################################################################################
+
+def process_path(path, origin_repo, config):
+ def join(prefix, ref):
+ assert ref.ref.startswith('refs/')
+ ref = '/'.join((prefix, path)) + '/-/' + ref.ref[len('refs/'):]
+ return origin_repo[ref]
+
+ origin_push = {}
+
+ base_url = config['base_url']
+ mirror_url = '[FILE-URL]' if base_url.startswith('file:') else origin_repo.url
+
+ subtree_repo = repo.Repo(posixpath.join(base_url, path))
+ subtree_repo.repos_dir = origin_repo.repos_dir
+ subtree_repo.reify(share_from=origin_repo)
+ subtree_repo.run('fetch', stdout=sys.stdout, stderr=sys.stderr)
+ subtree_repo_push = {}
+
+ synthed_count = 0
+
+ for glob in config['enabled_refglobs']:
+ for ref in origin_repo.refglob(glob):
+ LOGGER.info('processing ref %s', ref)
+ processed = join(config['subtree_processed_prefix'], ref)
+ synthed = join(config['subtree_synthesized_prefix'], ref)
+
+ synth_parent = synthed.commit
+ cur_tree = synthed.commit.data.tree
+ LOGGER.info('starting with tree %r', cur_tree)
+
+ for commit in processed.to(ref):
+ LOGGER.info('processing commit %s', commit)
+ try:
+ obj_name = '{.hsh}:{}'.format(commit, path)
+ typ = origin_repo.run('cat-file', '-t', obj_name).strip()
+ if typ != 'tree':
+ LOGGER.warn('path %r is not a tree in commit %s', path, commit)
+ continue
+ dir_tree = origin_repo.run('rev-parse', obj_name).strip()
+ except CalledProcessError:
+ continue
+
+ if dir_tree != cur_tree:
+ LOGGER.info('found new tree %r', dir_tree)
+ cur_tree = dir_tree
+ synthed_count += 1
+ synth_parent = commit.alter(
+ parents=[synth_parent.hsh] if synth_parent is not INVALID else [],
+ tree=dir_tree,
+ footers=collections.OrderedDict([
+ ('Cr-Mirrored-From', [mirror_url]),
+ ('Cr-Mirrored-Commit', [commit.hsh]),
+ ('Cr-Mirrored-Subtree', [path]),
+ ]),
+ )
+ origin_push[synthed] = synth_parent
+ subtree_repo_push[subtree_repo[ref.ref]] = synth_parent
+
+ origin_push[processed] = ref.commit
+
+ success = True
+ try:
+ # because the hashes are deterministic based on the real history, if the
+ # first push succeeds, but the second does not, it just means we'll end up
+ # doing a bit of extra work on the next loop, but correctness will still be
+ # ensured
+
+ # TODO(iannucci): Return the pushspecs from this method, and then thread
+ # the dispatches to subtree_repo. Additionally, can batch the origin_repo
+ # pushes (and push them serially in batches as the subtree_repo pushes
+ # complete). As long as each (sub, origin) push happens in order, it doesn't
+ # matter what else gets pushed along with it.
+ subtree_repo.fast_forward_push(subtree_repo_push)
+ origin_repo.fast_forward_push(origin_push)
+ except Exception: # pragma: no cover
+ LOGGER.exception('Caught exception while pushing in process_path')
+ success = False
+
+ return success, synthed_count
+
+
+def inner_loop(origin_repo, config):
+ """Returns (success, {path: #commits_synthesized})."""
+
+ LOGGER.debug('fetching %r', origin_repo)
+ origin_repo.run('fetch', stdout=sys.stdout, stderr=sys.stderr)
+ config.evaluate()
+
+ success = True
+ processed = {}
+ for path in config['enabled_paths']:
+ LOGGER.info('processing path %s', path)
+ try:
+ path_success, num_synthed = process_path(path, origin_repo, config)
+ success = path_success and success
+ processed[path] = num_synthed
+ except Exception: # pragma: no cover
+ LOGGER.exception('Caught in inner_loop')
+ success = False
+
+ return success, processed
« no previous file with comments | « infra/services/gsubtreed/__main__.py ('k') | infra/services/gsubtreed/test/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698