OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2016 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 | |
7 from dashboard.common import namespaced_stored_object | |
8 from dashboard.services import gitiles_service | |
9 | |
10 | |
11 _REPOSITORIES_KEY = 'repositories' | |
12 | |
13 | |
14 class NonLinearError(Exception): | |
15 """Raised when trying to find the midpoint of Changes that are not linear.""" | |
16 | |
17 | |
18 class AdjacentCommitsWarning(Exception): | |
perezju
2017/09/12 10:59:54
I'm not trilled about "raising" warnings as except
dtu
2017/09/12 22:31:16
Done.
| |
19 """Raised when trying to find the midpoint of Commits that are adjacent.""" | |
20 | |
21 | |
22 class SameCommitWarning(Exception): | |
23 """Raised when trying to find the midpoint of identical Commits.""" | |
24 | |
25 | |
26 class Commit(collections.namedtuple('Commit', ('repository', 'git_hash'))): | |
27 """A git repository pinned to a particular commit.""" | |
28 | |
29 def __str__(self): | |
30 return self.repository + '@' + self.git_hash[:7] | |
31 | |
32 @property | |
33 def id_string(self): | |
34 return self.repository + '@' + self.git_hash | |
35 | |
36 @property | |
37 def repository_url(self): | |
38 """The HTTPS URL of the repository as passed to `git clone`.""" | |
39 repositories = namespaced_stored_object.Get(_REPOSITORIES_KEY) | |
40 return repositories[self.repository]['repository_url'] | |
41 | |
42 def Deps(self): | |
43 """Return the DEPS of this Commit as a frozenset of Commits.""" | |
44 # Download and execute DEPS file. | |
45 deps_file_contents = gitiles_service.FileContents( | |
46 self.repository_url, self.git_hash, 'DEPS') | |
47 deps_data = {'Var': lambda variable: deps_data['vars'][variable]} | |
48 exec deps_file_contents in deps_data # pylint: disable=exec-used | |
49 | |
50 # Pull out deps dict, including OS-specific deps. | |
51 deps_dict = deps_data['deps'] | |
52 for deps_os in deps_data.get('deps_os', {}).itervalues(): | |
53 deps_dict.update(deps_os) | |
54 | |
55 # Convert deps strings to Commit objects. | |
56 commits = [] | |
57 for dep_string in deps_dict.itervalues(): | |
58 dep_string_parts = dep_string.split('@') | |
59 if len(dep_string_parts) < 2: | |
60 continue # Dep is not pinned to any particular revision. | |
61 if len(dep_string_parts) > 2: | |
62 raise NotImplementedError('Unknown DEP format: ' + dep_string) | |
63 | |
64 repository_url, git_hash = dep_string_parts | |
65 repository = _Repository(repository_url) | |
66 if not repository: | |
67 _AddRepository(repository_url) | |
perezju
2017/09/12 10:59:54
nit: make _AddRepository to return the repository
dtu
2017/09/12 22:31:16
Done.
| |
68 repository = _Repository(repository_url) | |
69 commits.append(Commit(repository, git_hash)) | |
70 | |
71 return frozenset(commits) | |
72 | |
73 def AsDict(self): | |
74 return { | |
75 'repository': self.repository, | |
76 'git_hash': self.git_hash, | |
77 'url': self.repository_url + '/+/' + self.git_hash, | |
78 } | |
79 | |
80 @classmethod | |
81 def FromDict(cls, data): | |
82 """Create a Commit from a dict. | |
83 | |
84 If the repository is a repository URL, it will be translated to its short | |
85 form name. | |
86 | |
87 Raises: | |
88 KeyError: The repository name is not in the local datastore, | |
89 or the git hash is not valid. | |
90 """ | |
91 repository = data['repository'] | |
92 | |
93 # Translate repository if it's a URL. | |
94 repository_from_url = _Repository(repository) | |
95 if repository_from_url: | |
96 repository = repository_from_url | |
perezju
2017/09/12 10:59:54
Wouldn't it be safer to check for leading 'https?:
dtu
2017/09/12 22:31:16
Done.
| |
97 | |
98 commit = cls(repository, data['git_hash']) | |
99 | |
100 try: | |
101 gitiles_service.CommitInfo(commit.repository_url, commit.git_hash) | |
102 except gitiles_service.NotFoundError as e: | |
103 raise KeyError(str(e)) | |
104 | |
105 return commit | |
106 | |
107 @classmethod | |
108 def Midpoint(cls, commit_a, commit_b): | |
109 """Return a Commit halfway between the two given Commits. | |
110 | |
111 Uses Gitiles to look up the commit range. | |
112 | |
113 Args: | |
114 commit_a: The first Commit in the range. | |
115 commit_b: The last Commit in the range. | |
116 | |
117 Returns: | |
118 A new Commit representing the midpoint. | |
119 The commit before the midpoint if the range has an even number of commits. | |
120 | |
121 Raises: | |
122 AdjacentCommitsWarning: The Commits are adjacent. | |
123 SameCommitWarning: The Commits are the same. | |
perezju
2017/09/12 10:59:54
I think these warnings raised as exceptions are aw
dtu
2017/09/12 22:31:16
Done.
| |
124 NonLinearError: The Commits are in different repositories or are in the | |
125 wrong order. | |
126 """ | |
127 if commit_a == commit_b: | |
128 raise SameCommitWarning() | |
129 | |
130 if commit_a.repository != commit_b.repository: | |
131 raise NonLinearError('Repositories differ between Commits: %s vs %s' % | |
132 (commit_a.repository, commit_b.repository)) | |
133 | |
134 commits = gitiles_service.CommitRange(commit_a.repository_url, | |
135 commit_a.git_hash, commit_b.git_hash) | |
136 # We don't handle NotFoundErrors because we assume that all Commits either | |
137 # came from this method or were already validated elsewhere. | |
138 if len(commits) == 0: | |
139 raise NonLinearError('The commits are in the wrong order: %s and %s' % | |
perezju
2017/09/12 10:59:54
nit: Rephrase as "Commit {a} does not come before
dtu
2017/09/12 22:31:16
Done. Do you mean e.g. they're in the same reposit
perezju
2017/09/13 12:52:23
Yep exactly. This may not be common since we tend
| |
140 commit_a, commit_b) | |
141 if len(commits) == 1: | |
142 raise AdjacentCommitsWarning() | |
143 commits = commits[1:] # Remove commit_b from the range. | |
144 | |
145 return cls(commit_a.repository, commits[len(commits) / 2]['commit']) | |
146 | |
147 | |
148 def _Repository(repository_url): | |
149 if repository_url.endswith('.git'): | |
150 repository_url = repository_url[:-4] | |
151 | |
152 repositories = namespaced_stored_object.Get(_REPOSITORIES_KEY) | |
153 for repo_label, repo_info in repositories.iteritems(): | |
154 if repository_url == repo_info['repository_url']: | |
155 return repo_label | |
156 | |
157 return None | |
158 | |
159 | |
160 def _AddRepository(repository_url): | |
161 if repository_url.endswith('.git'): | |
162 repository_url = repository_url[:-4] | |
163 | |
164 repositories = namespaced_stored_object.Get(_REPOSITORIES_KEY) | |
165 repository = repository_url.split('/')[-1] | |
166 | |
167 if repository in repositories: | |
168 raise AssertionError("Attempted to add a repository that's already in the " | |
169 'Datastore: %s: %s' % (repository, repository_url)) | |
170 | |
171 repositories[repository] = {'repository_url': repository_url} | |
172 namespaced_stored_object.Set(_REPOSITORIES_KEY, repositories) | |
OLD | NEW |