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

Unified Diff: dashboard/dashboard/pinpoint/models/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 side-by-side diff with in-line comments
Download patch
Index: dashboard/dashboard/pinpoint/models/change.py
diff --git a/dashboard/dashboard/pinpoint/models/change.py b/dashboard/dashboard/pinpoint/models/change.py
deleted file mode 100644
index bf13aa3203adb99eadb08492cc3820599f19cd2e..0000000000000000000000000000000000000000
--- a/dashboard/dashboard/pinpoint/models/change.py
+++ /dev/null
@@ -1,334 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import collections
-
-from dashboard.common import namespaced_stored_object
-from dashboard.services import gitiles_service
-
-
-_REPOSITORIES_KEY = 'repositories'
-
-
-class NonLinearError(Exception):
- """Raised when trying to find the midpoint of Changes that are not linear."""
-
-
-class Change(collections.namedtuple('Change',
- ('base_commit', 'deps', 'patch'))):
- """A particular set of Deps with or without an additional patch applied.
-
- For example, a Change might sync to src@9064a40 and catapult@8f26966,
- then apply patch 2423293002.
- """
-
- def __new__(cls, base_commit, deps=frozenset(), patch=None):
- """Create a Change.
-
- Args:
- base_commit: A Dep representing the initial commit to sync to. The DEPS
- file at that commit implies the default commits for any dependencies.
- deps: An optional iterable of Deps to override the dependencies implied
- by base_commit.
- patch: An optional Patch to apply to the Change.
- """
- return super(Change, cls).__new__(cls, base_commit, frozenset(deps), patch)
-
- def __str__(self):
- string = ' '.join(str(dep) for dep in self.all_deps)
- if self.patch:
- string += ' + ' + str(self.patch)
- return string
-
- @property
- def id_string(self):
- string = ' '.join(dep.id_string for dep in self.all_deps)
- if self.patch:
- string += ' + ' + self.patch.id_string
- return string
-
- @property
- def all_deps(self):
- return tuple([self.base_commit] + sorted(self.deps))
-
- def AsDict(self):
- return {
- 'base_commit': self.base_commit.AsDict(),
- 'deps': [dep.AsDict() for dep in sorted(self.deps)],
- 'patch': self.patch.AsDict() if self.patch else None,
- }
-
- @classmethod
- def FromDict(cls, data):
- base_commit = Dep.FromDict(data['base_commit'])
-
- kwargs = {}
- if 'deps' in data:
- kwargs['deps'] = tuple(Dep.FromDict(dep) for dep in data['deps'])
- if 'patch' in data:
- kwargs['patch'] = Patch.FromDict(data['patch'])
-
- return cls(base_commit, **kwargs)
-
- @classmethod
- def Midpoint(cls, change_a, change_b):
- """Return a Change halfway between the two given Changes.
-
- A NonLinearError is raised if the Changes are not linear. The Changes are
- not linear if any of the following is true:
- * They have different base repositories.
- * They have different patches.
- * Their repositories differ even after expanding DEPS rolls.
- See change_test.py for examples of linear and nonlinear Changes.
-
- The behavior is undefined if either of the Changes have multiple Deps with
- the same repository.
-
- 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 commit before the midpoint if the range has an even number of commits.
- None if the range is empty, or the Changes are given in the wrong order.
-
- Raises:
- NonLinearError: The Changes are not linear.
- """
- if change_a.base_commit.repository != change_b.base_commit.repository:
- raise NonLinearError(
- 'Change A has base repo "%s" and Change B has base repo "%s".' %
- (change_a.base_commit.repository, change_b.base_commit.repository))
-
- if change_a.patch != change_b.patch:
- raise NonLinearError(
- 'Change A has patch "%s" and Change B has patch "%s".' %
- (change_a.patch, change_b.patch))
-
- if change_a == change_b:
- return None
-
- # Find the midpoint of every pair of Deps, expanding DEPS rolls as we go.
- midpoint_deps = {}
-
- repositories_a = {dep.repository: dep for dep in change_a.all_deps}
- repositories_b = {dep.repository: dep for dep in change_b.all_deps}
-
- # Match up all the Deps by repository.
- while frozenset(repositories_a.iterkeys()).intersection(
- frozenset(repositories_b.iterkeys())):
- # Choose an arbitrary pair of Deps with the same repository.
- shared_repositories = set(repositories_a.iterkeys()).intersection(
- set(repositories_b.iterkeys()))
- repository = shared_repositories.pop()
- dep_a = repositories_a.pop(repository)
- dep_b = repositories_b.pop(repository)
-
- if dep_a == dep_b:
- # The Deps are the same.
- midpoint_deps[repository] = dep_a
- continue
-
- midpoint_dep = Dep.Midpoint(dep_a, dep_b)
- if midpoint_dep:
- # The Deps are not adjacent.
- midpoint_deps[repository] = midpoint_dep
- continue
-
- # The Deps are adjacent. Figure out if it's a DEPS roll.
- deps_a = dep_a.Deps()
- deps_b = dep_b.Deps()
- if deps_a == deps_b:
- # Not a DEPS roll. The Changes really are adjacent.
- return None
-
- # DEPS roll! Expand the roll.
- for dep in deps_a.difference(deps_b):
- if dep.repository in midpoint_deps:
- raise NonLinearError('Tried to take the midpoint across a DEPS roll, '
- 'but the underlying Dep is already overriden in '
- 'both Changes.')
- if dep.repository not in repositories_a:
- repositories_a[dep.repository] = dep
- for dep in deps_b.difference(deps_a):
- if dep.repository in midpoint_deps:
- raise NonLinearError('Tried to take the midpoint across a DEPS roll, '
- 'but the underlying Dep is already overriden in '
- 'both Changes.')
- if dep.repository not in repositories_b:
- repositories_b[dep.repository] = dep
- midpoint_deps[repository] = dep_a
-
- # Now that the DEPS are expanded, check to see if the repositories differ.
- if repositories_a or repositories_b:
- raise NonLinearError(
- 'Repositories differ between Change A and Change B: %s' %
- ', '.join(sorted(repositories_a.keys() + repositories_b.keys())))
-
- # Create our new Change!
- base_commit = midpoint_deps.pop(change_a.base_commit.repository)
- return cls(base_commit, midpoint_deps.itervalues(), change_a.patch)
-
-
-class Dep(collections.namedtuple('Dep', ('repository', 'git_hash'))):
- """A git repository pinned to a particular commit."""
-
- def __str__(self):
- return self.repository + '@' + self.git_hash[:7]
-
- @property
- def id_string(self):
- return self.repository + '@' + self.git_hash
-
- @property
- def repository_url(self):
- """The HTTPS URL of the repository as passed to `git clone`."""
- repositories = namespaced_stored_object.Get(_REPOSITORIES_KEY)
- return repositories[self.repository]['repository_url']
-
- def Deps(self):
- """Return the DEPS of this Dep as a frozenset of Deps."""
- # Download and execute DEPS file.
- deps_file_contents = gitiles_service.FileContents(
- self.repository_url, self.git_hash, 'DEPS')
- deps_data = {'Var': lambda variable: deps_data['vars'][variable]}
- exec deps_file_contents in deps_data # pylint: disable=exec-used
-
- # Pull out deps dict, including OS-specific deps.
- deps_dict = deps_data['deps']
- for deps_os in deps_data.get('deps_os', {}).itervalues():
- deps_dict.update(deps_os)
-
- # Convert deps strings to Dep objects.
- deps = []
- for dep_string in deps_dict.itervalues():
- dep_string_parts = dep_string.split('@')
- if len(dep_string_parts) < 2:
- continue # Dep is not pinned to any particular revision.
- if len(dep_string_parts) > 2:
- raise NotImplementedError('Unknown DEP format: ' + dep_string)
-
- repository_url, git_hash = dep_string_parts
- repository = _Repository(repository_url)
- if not repository:
- _AddRepository(repository_url)
- repository = _Repository(repository_url)
- deps.append(Dep(repository, git_hash))
-
- return frozenset(deps)
-
- def AsDict(self):
- return {
- 'repository': self.repository,
- 'git_hash': self.git_hash,
- 'url': self.repository_url + '/+/' + self.git_hash,
- }
-
- @classmethod
- def FromDict(cls, data):
- """Create a Dep from a dict.
-
- If the repository is a repository URL, it will be translated to its short
- form name.
-
- Raises:
- KeyError: The repository name is not in the local datastore,
- or the git hash is not valid.
- """
- repository = data['repository']
-
- # Translate repository if it's a URL.
- repository_from_url = _Repository(repository)
- if repository_from_url:
- repository = repository_from_url
-
- dep = cls(repository, data['git_hash'])
-
- try:
- gitiles_service.CommitInfo(dep.repository_url, dep.git_hash)
- except gitiles_service.NotFoundError as e:
- raise KeyError(str(e))
-
- return dep
-
- @classmethod
- def Midpoint(cls, dep_a, dep_b):
- """Return a Dep halfway between the two given Deps.
-
- Uses Gitiles to look up the commit range.
-
- Args:
- dep_a: The first Dep in the range.
- dep_b: The last Dep in the range.
-
- Returns:
- A new Dep representing the midpoint.
- The commit before the midpoint if the range has an even number of commits.
- None if the range is empty, or the Deps are given in the wrong order.
-
- Raises:
- ValueError: The Deps are in different repositories.
- """
- if dep_a.repository != dep_b.repository:
- raise ValueError("Can't find the midpoint of Deps in differing "
- 'repositories: "%s" and "%s"' % (dep_a, dep_b))
-
- commits = gitiles_service.CommitRange(dep_a.repository_url,
- dep_a.git_hash, dep_b.git_hash)
- # We don't handle NotFoundErrors because we assume that all Deps either came
- # from this method or were already validated elsewhere.
- if len(commits) <= 1:
- return None
- commits = commits[1:] # Remove dep_b from the range.
-
- return cls(dep_a.repository, commits[len(commits) / 2]['commit'])
-
-
-class Patch(collections.namedtuple('Patch', ('server', 'issue', 'patchset'))):
- """A patch in Rietveld."""
- # TODO: Support Gerrit.
- # https://github.com/catapult-project/catapult/issues/3599
-
- def __str__(self):
- return self.id_string
-
- @property
- def id_string(self):
- return '%s/%d/%d' % (self.server, self.issue, self.patchset)
-
- def AsDict(self):
- return self._asdict()
-
- @classmethod
- def FromDict(cls, data):
- # TODO: Validate to ensure the patch exists on the server.
- return cls(data['server'], data['issue'], data['patchset'])
-
-
-def _Repository(repository_url):
- if repository_url.endswith('.git'):
- repository_url = repository_url[:-4]
-
- repositories = namespaced_stored_object.Get(_REPOSITORIES_KEY)
- for repo_label, repo_info in repositories.iteritems():
- if repository_url == repo_info['repository_url']:
- return repo_label
-
- return None
-
-
-def _AddRepository(repository_url):
- if repository_url.endswith('.git'):
- repository_url = repository_url[:-4]
-
- repositories = namespaced_stored_object.Get(_REPOSITORIES_KEY)
- repository = repository_url.split('/')[-1]
-
- if repository in repositories:
- raise AssertionError("Attempted to add a repository that's already in the "
- 'Datastore: %s: %s' % (repository, repository_url))
-
- repositories[repository] = {'repository_url': repository_url}
- namespaced_stored_object.Set(_REPOSITORIES_KEY, repositories)

Powered by Google App Engine
This is Rietveld 408576698