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

Side by Side Diff: dashboard/dashboard/pinpoint/models/change/change.py

Issue 3013013002: [pinpoint] Change refactor. (Closed)
Patch Set: UI Created 3 years, 3 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 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 import itertools
7
8 from dashboard.pinpoint.models.change import commit as commit_module
9 from dashboard.pinpoint.models.change import patch as patch_module
10
11
12 class Change(collections.namedtuple('Change', ('commits', 'patch'))):
13 """A particular set of Commits with or without an additional patch applied.
14
15 For example, a Change might sync to src@9064a40 and catapult@8f26966,
16 then apply patch 2423293002.
17 """
18
19 def __new__(cls, commits, patch=None):
20 """Creates a Change.
21
22 Args:
23 commits: An iterable of Commits representing this Change's dependencies.
24 patch: An optional Patch to apply to the Change.
25 """
26 if not commits:
27 raise TypeError('At least one commit required.')
28 return super(Change, cls).__new__(cls, tuple(commits), patch)
29
30 def __str__(self):
31 """Returns an informal short string representation of this Change."""
32 string = ' '.join(str(commit) for commit in self.commits)
33 if self.patch:
34 string += ' + ' + str(self.patch)
35 return string
36
37 @property
38 def id_string(self):
39 """Returns a string that is unique to this list of commits and patch."""
40 string = ' '.join(commit.id_string for commit in self.commits)
41 if self.patch:
42 string += ' + ' + self.patch.id_string
43 return string
44
45 @property
46 def base_commit(self):
47 return self.commits[0]
48
49 @property
50 def last_commit(self):
51 return self.commits[-1]
52
53 @property
54 def deps(self):
55 return tuple(self.commits[1:])
56
57 def AsDict(self):
58 return {
59 'commits': [commit.AsDict() for commit in self.commits],
60 'patch': self.patch.AsDict() if self.patch else None,
61 }
62
63 @classmethod
64 def FromDict(cls, data):
65 commits = tuple(commit_module.Commit.FromDict(commit)
66 for commit in data['commits'])
67 if 'patch' in data:
68 patch = patch_module.Patch.FromDict(data['patch'])
69 else:
70 patch = None
71
72 return cls(commits, patch=patch)
73
74 @classmethod
75 def Midpoint(cls, change_a, change_b):
76 """Returns a Change halfway between the two given Changes.
77
78 This function does two passes over the Changes' Commits:
79 * The first pass attempts to match the lengths of the Commit lists by
80 expanding DEPS to fill in any repositories that are missing from one,
81 but included in the other.
82 * The second pass takes the midpoint of every matched pair of Commits,
83 expanding DEPS rolls as it comes across them.
84
85 A NonLinearError is raised if there is no valid midpoint. The Changes are
86 not linear if any of the following is true:
87 * They have different patches.
88 * Their repositories don't match even after expanding DEPS rolls.
89 * The left Change comes after the right Change.
90 * They are the same or adjacent.
91 See change_test.py for examples of linear and nonlinear Changes.
92
93 Args:
94 change_a: The first Change in the range.
95 change_b: The last Change in the range.
96
97 Returns:
98 A new Change representing the midpoint.
99 The Change before the midpoint if the range has an even number of commits.
100
101 Raises:
102 NonLinearError: The Changes are not linear.
103 """
104 if change_a.patch != change_b.patch:
105 raise commit_module.NonLinearError(
106 'Change A has patch "%s" and Change B has patch "%s".' %
107 (change_a.patch, change_b.patch))
108
109 commits_a = list(change_a.commits)
110 commits_b = list(change_b.commits)
111
112 _ExpandDepsToMatchRepositories(commits_a, commits_b)
113 commits_midpoint = _FindMidpoints(commits_a, commits_b)
114
115 if commits_a == commits_midpoint:
116 raise commit_module.NonLinearError('Changes are the same or adjacent.')
117
118 return cls(commits_midpoint, change_a.patch)
119
120
121 def _ExpandDepsToMatchRepositories(commits_a, commits_b):
122 """Expands DEPS in a Commit list to match the repositories in another.
123
124 Given two lists of Commits, with one bigger than the other, this function
125 looks through the DEPS files for smaller commit list to fill out any missing
126 Commits that are already in the bigger commit list.
127
128 Mutates the lists in-place, and doesn't return anything. The lists will not
129 have the same size if one Commit list contains a repository that is not found
130 in the DEPS of the other Commit list.
perezju 2017/09/13 12:52:24 (Let's revisit again on a follow up CL) Add a tes
dtu 2017/09/13 15:49:26 testDifferingCommitCount is the test for this case
131
132 Example:
133 commits_a == [chromium@a, v8@c]
134 commits_b == [chromium@b]
135 This function looks through the DEPS file at chromium@b to find v8, then
136 appends that v8 Commit to commits_b, making the lists match.
137
138 Args:
139 commits_a: A list of Commits.
140 commits_b: A list of Commits.
141 """
142 # The lists may be given in any order. Let's make commits_b the bigger list.
143 if len(commits_a) > len(commits_b):
144 commits_a, commits_b = commits_b, commits_a
145
146 # Loop through every DEPS file in commits_a.
147 for commit_a in commits_a:
148 if len(commits_a) == len(commits_b):
149 break
150 deps_a = commit_a.Deps()
151
152 # Look through commits_b for any extra slots to fill with the DEPS.
153 for commit_b in commits_b[len(commits_a):]:
154 dep_a = _FindCommitWithRepository(deps_a, commit_b.repository)
155 if dep_a:
156 commits_a.append(dep_a)
157 else:
158 break
159
160
161 def _FindMidpoints(commits_a, commits_b):
162 """Returns the midpoint of two Commit lists.
163
164 Loops through each pair of Commits and takes the midpoint. If the repositories
165 don't match, a NonLinearError is raised. If the Commits are adjacent and
166 represent a DEPS roll, the differing DEPs are added to the end of the lists.
167
168 Args:
169 commits_a: A list of Commits.
170 commits_b: A list of Commits.
171
172 Returns:
173 A list of Commits, each of which is the midpoint of the respective Commit in
174 commits_a and commits_b.
175
176 Raises:
177 NonLinearError: The lists have a different number of commits even after
178 expanding DEPS rolls, a Commit pair contains differing repositories, or a
179 Commit pair is in the wrong order.
180 """
181 commits_midpoint = []
182
183 for commit_a, commit_b in itertools.izip_longest(commits_a, commits_b):
184 if not (commit_a and commit_b):
185 # If the commit lists are not the same length, bail out. That could happen
186 # if commits_b has a repository that was not found in the DEPS of
187 # commits_a (or vice versa); or a DEPS roll added or removed a DEP.
188 raise commit_module.NonLinearError(
189 'Changes have a different number of commits.')
190
191 commit_midpoint = commit_module.Commit.Midpoint(commit_a, commit_b)
192 commits_midpoint.append(commit_midpoint)
193 if commit_a == commit_midpoint != commit_b:
194 # Commits are adjacent.
195 # Add any DEPS changes to the commit lists.
196 deps_a = commit_a.Deps()
197 deps_b = commit_b.Deps()
198 commits_a += sorted(
perezju 2017/09/13 12:52:24 :( :( :(
dtu 2017/09/13 15:49:26 :( :( :(
199 dep for dep in deps_a.difference(deps_b)
200 if not _FindCommitWithRepository(commits_a, dep.repository))
201 commits_b += sorted(
202 dep for dep in deps_b.difference(deps_a)
203 if not _FindCommitWithRepository(commits_b, dep.repository))
204
205 return commits_midpoint
206
207
208 def _FindCommitWithRepository(commits, repository):
209 for commit in commits:
210 if commit.repository == repository:
211 return commit
212 return None
OLDNEW
« no previous file with comments | « dashboard/dashboard/pinpoint/models/change/__init__.py ('k') | dashboard/dashboard/pinpoint/models/change/change_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698