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

Side by Side 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
37 # e.g. while processing the subtree 'b/foo' on refs/heads/master
38 # refs/heads/master <- real commits
39 # refs/subtree-processed/b/foo/-/heads/master <- ancestor tag of master
40 # refs/subtree-synthesized/b/foo/-/heads/master <- ref with synth commits
41 # For the sake of implementation simplicity, this daemon assumes the
42 # googlesource.com guarantee of transactional multi-ref pushes within a
43 # single repo.
44 'subtree_processed_prefix': 'refs/subtree-processed',
45 'subtree_synthesized_prefix': 'refs/subtree-synthesized',
46
47 # The base URL is the url relative to which all mirror repos are assumed to
48 # exist. For example, if you mirror the path 'bob', and base_url is
49 # https://host.domain.tld/main_repo, then it would assume that the mirror
50 # for the bob subtree is https://host.domain.tld/main_repo/bob.
51 #
52 # By default, base_url is set to the repo that gsubtreed is processing
53 'base_url': None,
54 'enabled_refglobs': ['refs/heads/*'],
55 'enabled_paths': [],
56 }
57 REF = 'refs/gsubtreed-config/main'
58
59
60
61 ################################################################################
62 # Core functionality
63 ################################################################################
64
65 def process_path(path, origin_repo, config):
66 def join(prefix, ref):
67 assert ref.ref.startswith('refs/')
68 ref = '/'.join((prefix, path)) + '/-/' + ref.ref[len('refs/'):]
69 return origin_repo[ref]
70
71 origin_push = {}
72
73 base_url = config['base_url']
74 mirror_url = '[FILE-URL]' if base_url.startswith('file:') else origin_repo.url
75
76 subtree_repo = repo.Repo(posixpath.join(base_url, path))
77 subtree_repo.repos_dir = origin_repo.repos_dir
78 subtree_repo.reify(share_from=origin_repo)
79 subtree_repo.run('fetch', stdout=sys.stdout, stderr=sys.stderr)
80 subtree_repo_push = {}
81
82 synthed_count = 0
83
84 for glob in config['enabled_refglobs']:
85 for ref in origin_repo.refglob(glob):
86 LOGGER.info('processing ref %s', ref)
87 processed = join(config['subtree_processed_prefix'], ref)
88 synthed = join(config['subtree_synthesized_prefix'], ref)
89
90 synth_parent = synthed.commit
91 cur_tree = synthed.commit.data.tree
92 LOGGER.info('starting with tree %r', cur_tree)
93
94 for commit in processed.to(ref):
95 LOGGER.info('processing commit %s', commit)
96 try:
97 obj_name = '{.hsh}:{}'.format(commit, path)
98 typ = origin_repo.run('cat-file', '-t', obj_name).strip()
99 if typ != 'tree':
100 LOGGER.warn('path %r is not a tree in commit %s', path, commit)
101 continue
102 dir_tree = origin_repo.run('rev-parse', obj_name).strip()
103 except CalledProcessError:
104 continue
105
106 if dir_tree != cur_tree:
107 LOGGER.info('found new tree %r', dir_tree)
108 cur_tree = dir_tree
109 synthed_count += 1
110 synth_parent = commit.alter(
111 parents=[synth_parent.hsh] if synth_parent is not INVALID else [],
112 tree=dir_tree,
113 footers=collections.OrderedDict([
114 ('Cr-Mirrored-From', [mirror_url]),
115 ('Cr-Mirrored-Commit', [commit.hsh]),
116 ('Cr-Mirrored-Subtree', [path]),
117 ]),
118 )
119 origin_push[synthed] = synth_parent
120 subtree_repo_push[subtree_repo[ref.ref]] = synth_parent
121
122 origin_push[processed] = ref.commit
123
124 success = True
125 try:
126 # because the hashes are deterministic based on the real history, if the
127 # first push succeeds, but the second does not, it just means we'll end up
128 # doing a bit of extra work on the next loop, but correctness will still be
129 # ensured
130
131 # TODO(iannucci): Return the pushspecs from this method, and then thread
132 # the dispatches to subtree_repo. Additionally, can batch the origin_repo
133 # pushes (and push them serially in batches as the subtree_repo pushes
134 # complete). As long as each (sub, origin) push happens in order, it doesn't
135 # matter what else gets pushed along with it.
136 subtree_repo.fast_forward_push(subtree_repo_push)
137 origin_repo.fast_forward_push(origin_push)
138 except Exception: # pragma: no cover
139 LOGGER.exception('Caught exception while pushing in process_path')
140 success = False
141
142 return success, synthed_count
143
144
145 def inner_loop(origin_repo, config):
146 """Returns (success, {path: #commits_synthesized})."""
147
148 LOGGER.debug('fetching %r', origin_repo)
149 origin_repo.run('fetch', stdout=sys.stdout, stderr=sys.stderr)
150 config.evaluate()
151
152 success = True
153 processed = {}
154 for path in config['enabled_paths']:
155 LOGGER.info('processing path %s', path)
156 try:
157 path_success, num_synthed = process_path(path, origin_repo, config)
158 success = path_success and success
159 processed[path] = num_synthed
160 except Exception: # pragma: no cover
161 LOGGER.exception('Caught in inner_loop')
162 success = False
163
164 return success, processed
OLDNEW
« 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