OLD | NEW |
---|---|
1 # Copyright 2016 The LUCI Authors. All rights reserved. | 1 # Copyright 2016 The LUCI Authors. All rights reserved. |
2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
4 | 4 |
5 import base64 | 5 import base64 |
6 import json | 6 import json |
7 import logging | 7 import logging |
8 import os | 8 import os |
9 import shutil | 9 import shutil |
10 import sys | 10 import sys |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
42 cmd = ['git'] | 42 cmd = ['git'] |
43 | 43 |
44 if checkout_dir is not None: | 44 if checkout_dir is not None: |
45 cmd += ['-C', checkout_dir] | 45 cmd += ['-C', checkout_dir] |
46 cmd += list(args) | 46 cmd += list(args) |
47 | 47 |
48 logging.info('Running: %s', cmd) | 48 logging.info('Running: %s', cmd) |
49 return subprocess42.check_output(cmd) | 49 return subprocess42.check_output(cmd) |
50 | 50 |
51 | 51 |
52 def ensure_git_checkout(repo, revision, checkout_dir, allow_fetch): | 52 class GitBackend(object): |
martiniss
2016/06/15 21:45:19
Just backend? Instead of git backend.
Paweł Hajdan Jr.
2016/06/17 12:30:25
Done.
| |
53 """Fetches given |repo| at |revision| to |checkout_dir| using git. | 53 def checkout(self, repo, revision, checkout_dir, allow_fetch): |
54 """Checks out given |repo| at |revision| to |checkout_dir|. | |
54 | 55 |
55 Network operations are performed only if |allow_fetch| is True. | 56 Network operations are performed only if |allow_fetch| is True. |
56 """ | 57 """ |
57 logging.info('Freshening repository %s in %s', repo, checkout_dir) | 58 raise NotImplementedError() |
58 | 59 |
59 if not os.path.isdir(checkout_dir): | 60 |
61 class LocalGitBackend(GitBackend): | |
62 """LocalGitBackend uses a local git checkout.""" | |
63 | |
64 def checkout(self, repo, revision, checkout_dir, allow_fetch): | |
65 logging.info('Freshening repository %s in %s', repo, checkout_dir) | |
66 | |
67 if not os.path.isdir(checkout_dir): | |
68 if not allow_fetch: | |
69 raise FetchNotAllowedError( | |
70 'need to clone %s but fetch not allowed' % repo) | |
71 _run_git(None, 'clone', '-q', repo, checkout_dir) | |
72 elif not os.path.isdir(os.path.join(checkout_dir, '.git')): | |
73 raise UncleanFilesystemError( | |
74 '%s exists but is not a git repo' % checkout_dir) | |
75 | |
76 actual_origin = _run_git( | |
77 checkout_dir, 'config', 'remote.origin.url').strip() | |
78 if actual_origin != repo: | |
79 raise UncleanFilesystemError( | |
80 ('workdir %r exists but uses a different origin url %r ' | |
81 'than requested %r') % (checkout_dir, actual_origin, repo)) | |
82 | |
83 try: | |
84 _run_git(checkout_dir, 'rev-parse', '-q', '--verify', | |
85 '%s^{commit}' % revision) | |
86 except subprocess42.CalledProcessError: | |
87 if not allow_fetch: | |
88 raise FetchNotAllowedError( | |
89 'need to fetch %s but fetch not allowed' % repo) | |
90 _run_git(checkout_dir, 'fetch') | |
91 _run_git(checkout_dir, 'reset', '-q', '--hard', revision) | |
92 | |
93 | |
94 class GitilesGitBackend(GitBackend): | |
95 """GitilesGitBackend uses a repo served by Gitiles.""" | |
96 | |
97 def checkout(self, repo, revision, checkout_dir, allow_fetch): | |
98 logging.info('Freshening repository %s in %s', repo, checkout_dir) | |
99 | |
100 # TODO(phajdan.jr): implement caching. | |
60 if not allow_fetch: | 101 if not allow_fetch: |
61 raise FetchNotAllowedError( | 102 raise FetchNotAllowedError( |
62 'need to clone %s but fetch not allowed' % repo) | 103 'need to download %s from gitiles but fetch not allowed' % repo) |
63 _run_git(None, 'clone', '-q', repo, checkout_dir) | |
64 elif not os.path.isdir(os.path.join(checkout_dir, '.git')): | |
65 raise UncleanFilesystemError( | |
66 '%s exists but is not a git repo' % checkout_dir) | |
67 | 104 |
68 actual_origin = _run_git(checkout_dir, 'config', 'remote.origin.url').strip() | 105 rev_url = '%s/+/%s?format=JSON' % (repo, requests.utils.quote(revision)) |
69 if actual_origin != repo: | 106 logging.info('fetching %s', rev_url) |
70 raise UncleanFilesystemError( | 107 rev_raw = requests.get(rev_url).text |
71 ('workdir %r exists but uses a different origin url %r ' | 108 if not rev_raw.startswith(')]}\'\n'): |
72 'than requested %r') % (checkout_dir, actual_origin, repo)) | 109 raise FetchError('Unexpected gitiles response: %s' % rev_raw) |
110 rev_json = json.loads(rev_raw.split('\n', 1)[1]) | |
111 orig_revision = revision | |
112 revision = rev_json['commit'] | |
113 logging.info('resolved %s to %s', orig_revision, revision) | |
73 | 114 |
74 try: | 115 shutil.rmtree(checkout_dir, ignore_errors=True) |
75 _run_git(checkout_dir, 'rev-parse', '-q', '--verify', | |
76 '%s^{commit}' % revision) | |
77 except subprocess42.CalledProcessError: | |
78 if not allow_fetch: | |
79 raise FetchNotAllowedError( | |
80 'need to fetch %s but fetch not allowed' % repo) | |
81 _run_git(checkout_dir, 'fetch') | |
82 _run_git(checkout_dir, 'reset', '-q', '--hard', revision) | |
83 | 116 |
117 recipes_cfg_url = '%s/+/%s/infra/config/recipes.cfg?format=TEXT' % ( | |
118 repo, requests.utils.quote(revision)) | |
119 logging.info('fetching %s' % recipes_cfg_url) | |
120 recipes_cfg_request = requests.get(recipes_cfg_url) | |
121 recipes_cfg_text = base64.b64decode(recipes_cfg_request.text) | |
122 recipes_cfg_proto = package_pb2.Package() | |
123 text_format.Merge(recipes_cfg_text, recipes_cfg_proto) | |
124 recipes_path_rel = recipes_cfg_proto.recipes_path | |
84 | 125 |
85 def ensure_gitiles_checkout(repo, revision, checkout_dir, allow_fetch): | 126 # Re-create recipes.cfg in |checkout_dir| so that the repo's recipes.py |
86 """Fetches given |repo| at |revision| to |checkout_dir| using gitiles. | 127 # can look it up. |
128 recipes_cfg_path = os.path.join( | |
129 checkout_dir, 'infra', 'config', 'recipes.cfg') | |
130 os.makedirs(os.path.dirname(recipes_cfg_path)) | |
131 with open(recipes_cfg_path, 'w') as f: | |
132 f.write(recipes_cfg_text) | |
87 | 133 |
88 Network operations are performed only if |allow_fetch| is True. | 134 recipes_path = os.path.join(checkout_dir, recipes_path_rel) |
89 """ | 135 os.makedirs(recipes_path) |
90 logging.info('Freshening repository %s in %s', repo, checkout_dir) | |
91 | 136 |
92 # TODO(phajdan.jr): implement caching. | 137 archive_url = '%s/+archive/%s/%s.tar.gz' % ( |
93 if not allow_fetch: | 138 repo, requests.utils.quote(revision), recipes_path_rel) |
94 raise FetchNotAllowedError( | 139 logging.info('fetching %s' % archive_url) |
95 'need to download %s from gitiles but fetch not allowed' % repo) | 140 archive_request = requests.get(archive_url) |
96 | 141 with tempfile.NamedTemporaryFile() as f: |
97 rev_url = '%s/+/%s?format=JSON' % (repo, requests.utils.quote(revision)) | 142 f.write(archive_request.content) |
98 logging.info('fetching %s', rev_url) | 143 f.flush() |
99 rev_raw = requests.get(rev_url).text | 144 with tarfile.open(f.name) as archive_tarfile: |
100 if not rev_raw.startswith(')]}\'\n'): | 145 archive_tarfile.extractall(recipes_path) |
101 raise FetchError('Unexpected gitiles response: %s' % rev_raw) | |
102 rev_json = json.loads(rev_raw.split('\n', 1)[1]) | |
103 orig_revision = revision | |
104 revision = rev_json['commit'] | |
105 logging.info('resolved %s to %s', orig_revision, revision) | |
106 | |
107 shutil.rmtree(checkout_dir, ignore_errors=True) | |
108 | |
109 recipes_cfg_url = '%s/+/%s/infra/config/recipes.cfg?format=TEXT' % ( | |
110 repo, requests.utils.quote(revision)) | |
111 logging.info('fetching %s' % recipes_cfg_url) | |
112 recipes_cfg_request = requests.get(recipes_cfg_url) | |
113 recipes_cfg_text = base64.b64decode(recipes_cfg_request.text) | |
114 recipes_cfg_proto = package_pb2.Package() | |
115 text_format.Merge(recipes_cfg_text, recipes_cfg_proto) | |
116 recipes_path_rel = recipes_cfg_proto.recipes_path | |
117 | |
118 # Re-create recipes.cfg in |checkout_dir| so that the repo's recipes.py | |
119 # can look it up. | |
120 recipes_cfg_path = os.path.join( | |
121 checkout_dir, 'infra', 'config', 'recipes.cfg') | |
122 os.makedirs(os.path.dirname(recipes_cfg_path)) | |
123 with open(recipes_cfg_path, 'w') as f: | |
124 f.write(recipes_cfg_text) | |
125 | |
126 recipes_path = os.path.join(checkout_dir, recipes_path_rel) | |
127 os.makedirs(recipes_path) | |
128 | |
129 archive_url = '%s/+archive/%s/%s.tar.gz' % ( | |
130 repo, requests.utils.quote(revision), recipes_path_rel) | |
131 logging.info('fetching %s' % archive_url) | |
132 archive_request = requests.get(archive_url) | |
133 with tempfile.NamedTemporaryFile() as f: | |
134 f.write(archive_request.content) | |
135 f.flush() | |
136 with tarfile.open(f.name) as archive_tarfile: | |
137 archive_tarfile.extractall(recipes_path) | |
OLD | NEW |