summaryrefslogtreecommitdiff
path: root/morphlib/plugins/diff_plugin.py
blob: 1f5870824cc4e3ac93bc0c252dd1e2b6bf34d934 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# -*- coding: utf-8 -*-
# Copyright © 2015  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.


from collections import defaultdict

import cliapp

from morphlib.buildcommand import BuildCommand
from morphlib.cmdline_parse_utils import parse_definition_lists
from morphlib.morphologyfinder import MorphologyFinder
from morphlib.morphloader import MorphologyLoader
from morphlib.morphset import MorphologySet
from morphlib.util import new_repo_caches


class DiffPlugin(cliapp.Plugin):

    def enable(self):
        self.app.add_subcommand(
            'definition-diff', self.definition_diff,
            arg_synopsis='REPO REF [SYSTEM]... - REPO REF [SYSTEM]...')

    def disable(self):
        pass

    def definition_diff(self, args):
        '''Show the difference between definitions.

        When given two definition file specifiers, prints the logical
        differences between the definitions.

        '''
        # NOTE: It would be more useful to have this operate at the graphed
        #       dependency level, so you could use it to compare two different
        #       systems, and avoid duplicated logic to interpret how
        #       definitions are parsed.
        #       However at the time of writing the data model does not support:
        #       1.  Separately loading definition files from (repo, ref) and
        #           having the loaded definitions being shared between computed
        #           sources.
        #       2.  Parsing definitions for multiple systems together.
        #           This is because parameters from the parent definition (i.e.
        #           arch) are passed down to included definitions, but this is
        #           not taken into account for the lookup table, which is keyed
        #           on name, so it can't handle two chunks with the same name
        #           but different architectures.
        from_spec, to_spec = parse_definition_lists(
                                 definition_list_name_list=('from', 'to'),
                                 args=args)

        bc = BuildCommand(self.app)

        def get_systems((reponame, ref, definitions)):
            'Convert a definition path list into a list of systems'
            ml = MorphologyLoader()
            repo = bc.lrc.get_updated_repo(reponame, ref=ref)
            mf = MorphologyFinder(gitdir=repo.gitdir, ref=ref)
            # We may have been given an empty set of definitions as input, in
            # which case we instead use every we find.
            if not definitions:
                definitions = mf.list_morphologies()
            system_paths = set()
            for definition in definitions:
                m = ml.parse_morphology_text(mf.read_morphology(definition),
                                             definition)
                if m.get('kind') == 'system' or 'strata' in m:
                    system_paths.add(definition)
            return reponame, ref, system_paths
        from_repo, from_ref, from_paths = get_systems(from_spec)
        to_repo, to_ref, to_paths = get_systems(to_spec)

        # This abuses Sources because they conflate the ideas of "logical
        # structure of the input of what we want to build" and the structure of
        # what the output of that would be.
        # If we were to pass the produced source pools to the artifact graphing
        # code then they would become an invalid structure of the output,
        # because the graphing code cannot handle multiple systems.
        from_sp = bc.create_source_pool(repo_name=from_repo, ref=from_ref,
                                        filenames=from_paths)
        to_sp = bc.create_source_pool(repo_name=to_repo, ref=to_ref,
                                      filenames=to_paths)

        from_by_name = defaultdict(set)
        to_by_name = defaultdict(set)
        for from_source in from_sp:
            from_by_name[from_source.morphology['name']].add(from_source)
        for to_source in to_sp:
            to_by_name[to_source.morphology['name']].add(to_source)

        for name, from_sources in from_by_name.iteritems():
            from_source = iter(from_sources).next()
            to_sources = to_by_name[name]
            if not to_sources:
                print('{} was removed'.format(name))
                continue
            # We don't actually care that multiple sources match that
            # lookup because of artifact splitting, so we'll take the
            # first.
            to_source = iter(to_sources).next()
            if from_source.repo_name != to_source.repo_name:
                print('{} repo changed from {} to {}'.format(
                    name, from_source.repo_name, to_source.repo_name))
            if from_source.original_ref != to_source.original_ref:
                from_repo, to_repo = (bc.lrc.get_updated_repo(s.repo_name,
                                                              ref=s.sha1)
                                      for s in (from_source, to_source))
                from_desc = from_repo.gitdir.version_guess(from_source.sha1)
                to_desc = to_repo.gitdir.version_guess(to_source.sha1)
                print('{} ref changed from {} to {}'.format(
                    from_source.morphology['name'], from_desc, to_desc))