OLD | NEW |
(Empty) | |
| 1 # Copyright 2014 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 copy |
| 7 |
| 8 |
| 9 from testing_support.git.util import OrderedSet |
| 10 |
| 11 |
| 12 class GitRepoSchema(object): |
| 13 """A declarative git testing repo. |
| 14 |
| 15 Pass a schema to __init__ in the form of: |
| 16 A B C D |
| 17 B E D |
| 18 |
| 19 This is the repo |
| 20 |
| 21 A - B - C - D |
| 22 \ E / |
| 23 |
| 24 Whitespace doesn't matter. Each line is a declaration of which commits come |
| 25 before which other commits. |
| 26 |
| 27 Every commit gets a tag 'tag_%(commit)s' |
| 28 Every unique terminal commit gets a branch 'branch_%(commit)s' |
| 29 Last commit in First line is the branch 'master' |
| 30 Root commits get a ref 'root_%(commit)s' |
| 31 |
| 32 Timestamps are in topo order, earlier commits (as indicated by their presence |
| 33 in the schema) get earlier timestamps. Stamps start at the Unix Epoch, and |
| 34 increment by 1 day each. |
| 35 """ |
| 36 COMMIT = collections.namedtuple('COMMIT', 'name parents is_branch is_root') |
| 37 |
| 38 def __init__(self, repo_schema='', |
| 39 content_fn=lambda v: {v: {'data': v}}): |
| 40 """Builds a new GitRepoSchema. |
| 41 |
| 42 Args: |
| 43 repo_schema (str) - Initial schema for this repo. See class docstring for |
| 44 info on the schema format. |
| 45 content_fn ((commit_name) -> commit_data) - A function which will be |
| 46 lazily called to obtain data for each commit. The results of this |
| 47 function are cached (i.e. it will never be called twice for the same |
| 48 commit_name). See the docstring on the GitRepo class for the format of |
| 49 the data returned by this function. |
| 50 """ |
| 51 self.master = None |
| 52 self.par_map = {} |
| 53 self.data_cache = {} |
| 54 self.content_fn = content_fn |
| 55 self.add_commits(repo_schema) |
| 56 |
| 57 def walk(self): |
| 58 """(Generator) Walks the repo schema from roots to tips. |
| 59 |
| 60 Generates GitRepoSchema.COMMIT objects for each commit. |
| 61 |
| 62 Throws an AssertionError if it detects a cycle. |
| 63 """ |
| 64 is_root = True |
| 65 par_map = copy.deepcopy(self.par_map) |
| 66 while par_map: |
| 67 empty_keys = set(k for k, v in par_map.iteritems() if not v) |
| 68 assert empty_keys, 'Cycle detected! %s' % par_map |
| 69 |
| 70 for k in sorted(empty_keys): |
| 71 yield self.COMMIT(k, self.par_map[k], |
| 72 not any(k in v for v in self.par_map.itervalues()), |
| 73 is_root) |
| 74 del par_map[k] |
| 75 for v in par_map.itervalues(): |
| 76 v.difference_update(empty_keys) |
| 77 is_root = False |
| 78 |
| 79 def add_partial(self, commit, parent=None): |
| 80 if commit not in self.par_map: |
| 81 self.par_map[commit] = OrderedSet() |
| 82 if parent is not None: |
| 83 self.par_map[commit].add(parent) |
| 84 |
| 85 def add_commits(self, schema): |
| 86 """Adds more commits from a schema into the existing Schema. |
| 87 |
| 88 Args: |
| 89 schema (str) - See class docstring for info on schema format. |
| 90 |
| 91 Throws an AssertionError if it detects a cycle. |
| 92 """ |
| 93 for commits in (l.split() for l in schema.splitlines() if l.strip()): |
| 94 parent = None |
| 95 for commit in commits: |
| 96 self.add_partial(commit, parent) |
| 97 parent = commit |
| 98 if parent and not self.master: |
| 99 self.master = parent |
| 100 for _ in self.walk(): # This will throw if there are any cycles. |
| 101 pass |
| 102 |
| 103 def data_for(self, commit): |
| 104 """Obtains the data for |commit|. |
| 105 |
| 106 See the docstring on the GitRepo class for the format of the returned data. |
| 107 |
| 108 Caches the result on this GitRepoSchema instance. |
| 109 """ |
| 110 if commit not in self.data_cache: |
| 111 self.data_cache[commit] = self.content_fn(commit) |
| 112 return self.data_cache[commit] |
| 113 |
| 114 def simple_graph(self): |
| 115 """Returns a dictionary of {commit_subject: {parent commit_subjects}} |
| 116 |
| 117 This allows you to get a very simple connection graph over the whole repo |
| 118 for comparison purposes. Only commit subjects (not ids, not content/data) |
| 119 are considered |
| 120 """ |
| 121 ret = {} |
| 122 for commit in self.walk(): |
| 123 ret.setdefault(commit.name, set()).update(commit.parents) |
| 124 return ret |
OLD | NEW |