diff options
-rw-r--r-- | morphlib/plugins/branch_and_merge_plugin.py | 91 | ||||
-rw-r--r-- | tests.branching/merge-handles-unmergable-cases.stderr | 2 |
2 files changed, 84 insertions, 9 deletions
diff --git a/morphlib/plugins/branch_and_merge_plugin.py b/morphlib/plugins/branch_and_merge_plugin.py index b14a9ef7..6d10346d 100644 --- a/morphlib/plugins/branch_and_merge_plugin.py +++ b/morphlib/plugins/branch_and_merge_plugin.py @@ -756,18 +756,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): @@ -837,17 +901,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'] @@ -863,7 +933,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'], @@ -875,6 +945,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] @@ -891,7 +966,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.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 |