diff options
5 files changed, 119 insertions, 9 deletions
diff --git a/morphlib/plugins/branch_and_merge_plugin.py b/morphlib/plugins/branch_and_merge_plugin.py index c2e70dc8..6713fce9 100644 --- a/morphlib/plugins/branch_and_merge_plugin.py +++ b/morphlib/plugins/branch_and_merge_plugin.py @@ -829,18 +829,82 @@ class BranchAndMergePlugin(cliapp.Plugin): base_morph = self.load_morphology(repo_dir, name, ref=base_sha1) from_morph = self.load_morphology(repo_dir, name, ref=from_sha1) to_morph = self.load_morphology(repo_dir, name, ref=to_ref) + return base_morph, from_morph, to_morph + + def check_component(self, parent_kind, parent_path, from_info, to_info): + assert (parent_kind in ['system', 'stratum']) + + kind = 'chunk' if parent_kind == 'stratum' else 'stratum' + name = to_info.get('alias', to_info.get('name', to_info.get('morph'))) + path = parent_path + '.' + name + + if kind == 'chunk': + # Only chunks can be petrified + from_unpetrify_ref = from_info.get('unpetrify-ref', None) + to_unpetrify_ref = to_info.get('unpetrify-ref', None) + if from_unpetrify_ref is not None and to_unpetrify_ref is None: + self.app.output.write( + 'WARNING: chunk "%s" is now petrified\n' % path) + elif from_unpetrify_ref is None and to_unpetrify_ref is not None: + self.app.output.write( + 'WARNING: chunk "%s" is no longer petrified\n' % path) + elif from_unpetrify_ref != to_unpetrify_ref: + raise cliapp.AppException( + 'merge conflict: chunk "%s" is petrified to a different ' + 'ref in each branch' % path) + + def diff_morphologies(self, path, from_morph, to_morph): + '''Component-level diff between two versions of a morphology''' + + def component_key(info): + # This function needs only to be stable and reproducible + key = info['repo'] + '|' + info['morph'] + if 'name' in info: + key += '|' + info['name'] + return key if from_morph['name'] != to_morph['name']: # We should enforce name == filename in load_morphology() raise cliapp.AppException( 'merge conflict: "name" of morphology %s (name should always ' - 'match filename)' % name) + 'match filename)' % path) if from_morph['kind'] != to_morph['kind']: raise cliapp.AppException( 'merge conflict: "kind" of morphology %s changed from %s to %s' - % (name, from_morph['kind'], to_morph['kind'])) - - return base_morph, from_morph, to_morph + % (path, to_morph['kind'], from_morph['kind'])) + + kind = to_morph['kind'] + + # copy() makes a shallow copy, so editing the list elements will + # change the actual morphologies. + if kind == 'system': + from_components = copy.copy(from_morph['strata']) + to_components = copy.copy(to_morph['strata']) + elif kind == 'stratum': + from_components = copy.copy(from_morph['chunks']) + to_components = copy.copy(to_morph['chunks']) + from_components.sort(key=component_key) + to_components.sort(key=component_key) + + # These are not set() purely because a set requires a hashable type + intersection = [] # TO n FROM + from_diff = [] # FROM \ TO + to_diff = [] # TO \ FROM + while len(from_components) > 0 and len(to_components) > 0: + from_info = from_components.pop(0) + to_info = to_components.pop(0) + match = cmp(component_key(from_info), component_key(to_info)) + if match < 0: + from_diff.append(from_info) + elif match > 0: + to_diff.append(to_info) + elif match == 0: + intersection.append((from_info, to_info)) + if len(from_components) != 0: + from_diff.append(from_components.pop(0)) + if len(to_components) != 0: + to_diff.append(to_components.pop(0)) + return intersection, from_diff, to_diff def merge_repo(self, merged_repos, from_branch_dir, from_repo, from_ref, to_branch_dir, to_repo, to_ref): @@ -910,17 +974,23 @@ class BranchAndMergePlugin(cliapp.Plugin): 'repository : %s vs %s' % (root_repo, other_root_repo)) - def merge_chunk(old_ci, ci): + def merge_chunk(parent_path, old_ci, ci): self.merge_repo(merged_repos, from_branch_dir, old_ci['repo'], from_branch, to_branch_dir, ci['repo'], ci['ref']) - def merge_stratum(old_si, si): + def merge_stratum(parent_path, old_si, si): + path = parent_path + '.' + si['morph'] + to_repo_dir, from_sha1 = self.merge_repo(merged_repos, from_branch_dir, old_si['repo'], from_branch, to_branch_dir, si['repo'], si['ref']) base_morph, from_morph, to_morph = self.get_merge_files( to_repo_dir, from_sha1, si['ref'], si['morph']) + intersection, from_diff, to_diff = self.diff_morphologies( + path, from_morph, to_morph) + for from_ci, to_ci in intersection: + self.check_component('stratum', path, from_ci, to_ci) changed = False edited_chunks = [ci for ci in from_morph['chunks'] @@ -936,7 +1006,7 @@ class BranchAndMergePlugin(cliapp.Plugin): 'refusing to merge.' % ci['name']) changed = True ci['ref'] = old_ci['ref'] - merge_chunk(old_ci, ci) + merge_chunk(path, old_ci, ci) if changed: self.save_morphology(to_repo_dir, si['morph'], to_morph) self.app.runcmd(['git', 'add', si['morph'] + '.morph'], @@ -948,6 +1018,11 @@ class BranchAndMergePlugin(cliapp.Plugin): if to_morph['kind'] != 'system': return + intersection, from_diff, to_diff = self.diff_morphologies( + name, from_morph, to_morph) + for from_si, to_si in intersection: + self.check_component('system', name, from_si, to_si) + changed = False edited_strata = [si for si in from_morph['strata'] if si['ref'] == from_branch] @@ -964,7 +1039,7 @@ class BranchAndMergePlugin(cliapp.Plugin): 'refusing to merge.' % si['morph']) changed = True si['ref'] = old_si['ref'] - merge_stratum(old_si, si) + merge_stratum(name, old_si, si) if changed: self.save_morphology(to_root_dir, name, to_morph) self.app.runcmd(['git', 'add', f], cwd=to_root_dir) diff --git a/tests.branching/merge-handles-unmergable-cases.script b/tests.branching/merge-handles-unmergable-cases.script index fd7f1478..e1ba5f9e 100755 --- a/tests.branching/merge-handles-unmergable-cases.script +++ b/tests.branching/merge-handles-unmergable-cases.script @@ -24,7 +24,9 @@ cd "$DATADIR/workspace" "$SRCDIR/scripts/test-morph" branch baserock:morphs test/unmergable cd "$DATADIR/workspace/test/unmergable/baserock:morphs" +"$SRCDIR/scripts/test-morph" edit hello-system hello-stratum sed -ie 's/"kind": "stratum"/"kind": "chunk"/' hello-stratum.morph git commit --quiet --all -m "Unmergeable because kind has changed" + cd "$DATADIR/workspace/master/baserock:morphs" "$SRCDIR/scripts/test-morph" merge test/unmergable diff --git a/tests.branching/merge-handles-unmergable-cases.stderr b/tests.branching/merge-handles-unmergable-cases.stderr index 64c9c34b..ff6539a7 100644 --- a/tests.branching/merge-handles-unmergable-cases.stderr +++ b/tests.branching/merge-handles-unmergable-cases.stderr @@ -1 +1 @@ -ERROR: merge conflict: "kind" of morphology hello-stratum changed from chunk to stratum +ERROR: merge conflict: "kind" of morphology hello-system.hello-stratum changed from stratum to chunk diff --git a/tests.branching/merge-warns-if-merging-petrified-morphologies.script b/tests.branching/merge-warns-if-merging-petrified-morphologies.script new file mode 100755 index 00000000..12e726ed --- /dev/null +++ b/tests.branching/merge-warns-if-merging-petrified-morphologies.script @@ -0,0 +1,32 @@ +#!/bin/sh +# Copyright (C) 2012 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# If the user merges a petrified branch into an unpetrified branch, +# we should warn them. + +set -eu + +cd "$DATADIR/workspace" +"$SRCDIR/scripts/test-morph" init +"$SRCDIR/scripts/test-morph" checkout baserock:morphs master +"$SRCDIR/scripts/test-morph" branch baserock:morphs test/petrified + +cd "$DATADIR/workspace/test/petrified/baserock:morphs" +"$SRCDIR/scripts/test-morph" petrify +git commit --quiet --all -m "Petrify branch" + +cd "$DATADIR/workspace/master/baserock:morphs" +"$SRCDIR/scripts/test-morph" merge test/petrified diff --git a/tests.branching/merge-warns-if-merging-petrified-morphologies.stdout b/tests.branching/merge-warns-if-merging-petrified-morphologies.stdout new file mode 100644 index 00000000..65985486 --- /dev/null +++ b/tests.branching/merge-warns-if-merging-petrified-morphologies.stdout @@ -0,0 +1 @@ +WARNING: chunk "hello-system.hello-stratum.hello" is now petrified |