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

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: test multiple runs, fix some logging silliness 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
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 '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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698