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

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

Issue 3013013002: [pinpoint] Change refactor. (Closed)
Patch Set: . 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 string = ' '.join(str(commit) for commit in self.commits)
32 if self.patch:
33 string += ' + ' + str(self.patch)
34 return string
35
36 @property
37 def id_string(self):
perezju 2017/09/12 10:59:54 add docstrings explaining difference between str(c
dtu 2017/09/12 22:31:16 Done.
38 string = ' '.join(commit.id_string for commit in self.commits)
39 if self.patch:
40 string += ' + ' + self.patch.id_string
41 return string
42
43 @property
44 def base_commit(self):
45 return self.commits[0]
46
47 @property
48 def deps(self):
49 return tuple(self.commits[1:])
50
51 def AsDict(self):
52 return {
53 'commits': [commit.AsDict() for commit in self.commits],
54 'patch': self.patch.AsDict() if self.patch else None,
55 }
56
57 @classmethod
58 def FromDict(cls, data):
59 commits = tuple(commit_module.Commit.FromDict(commit)
60 for commit in data['commits'])
61 if 'patch' in data:
62 patch = patch_module.Patch.FromDict(data['patch'])
63 else:
64 patch = None
65
66 return cls(commits, patch=patch)
67
68 @classmethod
69 def Midpoint(cls, change_a, change_b):
70 """Returns a Change halfway between the two given Changes.
71
72 This function does two passes over the Changes' Commits:
73 * The first pass attempts to match the lengths of the Commit lists by
74 expanding DEPS to fill in any repositories that are missing from one,
75 but included in the other.
76 * The second pass takes the midpoint of every matched pair of Commits,
77 expanding DEPS rolls as it comes across them.
78
79 A NonLinearError is raised if there is no valid midpoint. The Changes are
80 not linear if any of the following is true:
81 * They have different patches.
82 * Their repositories don't match even after expanding DEPS rolls.
83 * The left Change comes after the right Change.
84 * They are the same or adjacent.
85 See change_test.py for examples of linear and nonlinear Changes.
86
87 Args:
88 change_a: The first Change in the range.
89 change_b: The last Change in the range.
90
91 Returns:
92 A new Change representing the midpoint.
93 The Change before the midpoint if the range has an even number of commits.
94
95 Raises:
96 NonLinearError: The Changes are not linear.
97 """
98 if change_a.patch != change_b.patch:
99 raise commit_module.NonLinearError(
100 'Change A has patch "%s" and Change B has patch "%s".' %
101 (change_a.patch, change_b.patch))
102
103 commits_a = list(change_a.commits)
104 commits_b = list(change_b.commits)
105
106 _ExpandDepsToMatchRepositories(commits_a, commits_b)
107 commits_midpoint = _FindMidpoints(commits_a, commits_b)
108
109 if commits_a == commits_midpoint:
110 raise commit_module.NonLinearError('Changes are the same or adjacent.')
111
112 return cls(commits_midpoint, change_a.patch)
113
114
115 def _ExpandDepsToMatchRepositories(commits_a, commits_b):
116 """Expands DEPS in a Commit list to match the repositories in another.
117
118 Given two lists of Commits, with one bigger than the other, this function
119 looks through the DEPS files for smaller commit list to fill out any missing
120 Commits that are already in the bigger commit list.
121
122 Mutates the lists in-place, and doesn't return anything.
123
124 Example:
125 commits_a == [chromium@a, v8@c]
126 commits_b == [chromium@b]
127 This function looks through the DEPS file at chromium@b to find v8, then
128 appends that v8 Commit to commits_b, making the lists match.
129
130 Args:
131 commits_a: A list of Commits.
132 commits_b: A list of Commits.
133 """
134 # The lists may be given in any order. Let's make commits_b the bigger list.
135 if len(commits_a) > len(commits_b):
136 commits_a, commits_b = commits_b, commits_a
137
138 # Loop through every DEPS file in commits_a.
139 for commit_a in commits_a:
perezju 2017/09/12 10:59:54 this looks scary, modifying the list while iterati
dtu 2017/09/12 22:31:16 Is it any less scary if we're not modifying the ex
perezju 2017/09/13 12:52:23 (Let's revisit again on a follow up CL) It's stil
dtu 2017/09/13 15:49:26 It keeps going. I've used izip specifically instea
140 if len(commits_a) == len(commits_b):
141 break
142 deps_a = commit_a.Deps()
143
144 # Look through commits_b for any extra slots to fill with the DEPS.
145 for commit_b in commits_b[len(commits_a):]:
146 dep_a = _FindCommitWithRepository(deps_a, commit_b.repository)
147 if dep_a:
148 commits_a.append(dep_a)
149 else:
150 break
151
152
153 def _FindMidpoints(commits_a, commits_b):
154 """Returns the midpoint of two Commit lists.
155
156 Loops through each pair of Commits and takes the midpoint. If the repositories
157 don't match, a NonLinearError is raised. If the Commits are adjacent and
158 represent a DEPS roll, the differing DEPs are added to the end of the lists.
159
160 Args:
161 commits_a: A list of Commits.
162 commits_b: A list of Commits.
163
164 Returns:
165 A list of Commits, each of which is the midpoint of the respective Commit in
166 commits_a and commits_b.
167
168 Raises:
169 NonLinearError: The lists have a different number of commits even after
170 expanding DEPS rolls, a Commit pair contains differing repositories, or a
171 Commit pair is in the wrong order.
172 """
173 commits_midpoint = []
174
175 for commit_a, commit_b in itertools.izip_longest(commits_a, commits_b):
perezju 2017/09/12 10:59:54 at this point the two lists should be the same siz
dtu 2017/09/12 22:31:16 In practical cases, yes, but there's no invariant
perezju 2017/09/13 12:52:23 (Let's revisit again on a follow up CL) I was goi
dtu 2017/09/13 15:49:26 That's right. If a DEPS roll adds or removes a DEP
176 if not (commit_a and commit_b):
177 raise commit_module.NonLinearError(
178 'Changes have a different number of commits.')
179
180 try:
181 # Commits are not adjacent.
182 commit_midpoint = commit_module.Commit.Midpoint(commit_a, commit_b)
183 except commit_module.SameCommitWarning:
184 # Commits are the same.
185 commit_midpoint = commit_a
186 except commit_module.AdjacentCommitsWarning:
187 # Commits are adjacent.
188 commit_midpoint = commit_a
189
190 # Add any DEPS changes to the commit lists.
191 deps_a = commit_a.Deps()
192 deps_b = commit_b.Deps()
193 commits_a += sorted(
194 dep for dep in deps_a.difference(deps_b)
195 if not _FindCommitWithRepository(commits_a, dep.repository))
196 commits_b += sorted(
197 dep for dep in deps_b.difference(deps_a)
198 if not _FindCommitWithRepository(commits_b, dep.repository))
199
200 commits_midpoint.append(commit_midpoint)
201
202 return commits_midpoint
203
204
205 def _FindCommitWithRepository(commits, repository):
206 for commit in commits:
207 if commit.repository == repository:
208 return commit
209 return None
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698