def Midpoint(cls, change_a, change_b): """Returns a Change halfway between the two given Changes. This function does two passes over the Changes' Commits: * The first pass attempts to match the lengths of the Commit lists by expanding DEPS to fill in any repositories that are missing from one, but included in the other. * The second pass takes the midpoint of every matched pair of Commits, expanding DEPS rolls as it comes across them. A NonLinearError is raised if there is no valid midpoint. The Changes are not linear if any of the following is true: * They have different patches. * Their repositories don't match even after expanding DEPS rolls. * The left Change comes after the right Change. * They are the same or adjacent. See change_test.py for examples of linear and nonlinear Changes. Args: change_a: The first Change in the range. change_b: The last Change in the range. Returns: A new Change representing the midpoint. The Change before the midpoint if the range has an even number of commits. Raises: NonLinearError: The Changes are not linear. """ if change_a.patch != change_b.patch: raise commit_module.NonLinearError( 'Change A has patch "%s" and Change B has patch "%s".' % (change_a.patch, change_b.patch)) commits_a = list(change_a.commits) commits_b = list(change_b.commits) _ExpandDepsToMatchRepositories(commits_a, commits_b) commits_midpoint = _FindMidpoints(commits_a, commits_b) if commits_a == commits_midpoint: raise commit_module.NonLinearError( 'Changes are the same or adjacent.') return cls(commits_midpoint, change_a.patch)
def _FindMidpoints(commits_a, commits_b): """Returns the midpoint of two Commit lists. Loops through each pair of Commits and takes the midpoint. If the repositories don't match, a NonLinearError is raised. If the Commits are adjacent and represent a DEPS roll, the differing DEPs are added to the end of the lists. Args: commits_a: A list of Commits. commits_b: A list of Commits. Returns: A list of Commits, each of which is the midpoint of the respective Commit in commits_a and commits_b. Raises: NonLinearError: The lists have a different number of commits even after expanding DEPS rolls, a Commit pair contains differing repositories, or a Commit pair is in the wrong order. """ commits_midpoint = [] for commit_a, commit_b in zip_longest(commits_a, commits_b): if not (commit_a and commit_b): # If the commit lists are not the same length, bail out. That could happen # if commits_b has a repository that was not found in the DEPS of # commits_a (or vice versa); or a DEPS roll added or removed a DEP. raise commit_module.NonLinearError( 'Changes have a different number of commits.') commit_midpoint = commit_module.Commit.Midpoint(commit_a, commit_b) commits_midpoint.append(commit_midpoint) if commit_a == commit_midpoint and commit_midpoint != commit_b: # Commits are adjacent. # Add any DEPS changes to the commit lists. deps_a = commit_a.Deps() deps_b = commit_b.Deps() dep_commits_a = sorted( commit_module.Commit.FromDep(dep) for dep in deps_a.difference(deps_b) if not _FindRepositoryUrlInCommits(commits_a, dep.repository_url)) dep_commits_b = sorted( commit_module.Commit.FromDep(dep) for dep in deps_b.difference(deps_a) if not _FindRepositoryUrlInCommits(commits_b, dep.repository_url)) commits_a += [c for c in dep_commits_a if c is not None] commits_b += [c for c in dep_commits_b if c is not None] return commits_midpoint