diff options
author | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2012-09-12 17:07:02 +0100 |
---|---|---|
committer | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2012-09-14 19:44:40 +0100 |
commit | f7a3f591a12baa17fa0380ba36405651fe2b0126 (patch) | |
tree | 6b4cd50ff096b3cc28b153bf86d53489a529de4a /morphlib/plugins | |
parent | d3d278b6bdade8141836bcd35a47d7476ae49ecc (diff) | |
download | morph-f7a3f591a12baa17fa0380ba36405651fe2b0126.tar.gz |
morph merge: Merge changes in morphology contents
We now load the previous version of the morphology and check for
differences, which allows us to handle components that were added or
removed between branches.
We currently disallow adding new chunks or strata in a branch if
they were 'edit'ed afterwards. We can fix this by having 'edit'
store the original ref in the repo, rather than reading it from
the old version of the morphology.
Diffstat (limited to 'morphlib/plugins')
-rw-r--r-- | morphlib/plugins/branch_and_merge_plugin.py | 107 |
1 files changed, 76 insertions, 31 deletions
diff --git a/morphlib/plugins/branch_and_merge_plugin.py b/morphlib/plugins/branch_and_merge_plugin.py index d05d7929..4f78551e 100644 --- a/morphlib/plugins/branch_and_merge_plugin.py +++ b/morphlib/plugins/branch_and_merge_plugin.py @@ -586,13 +586,38 @@ class BranchAndMergePlugin(cliapp.Plugin): self.print_changelog('The following changes were made but have not ' 'been comitted') + def load_morphology_pair(self, repo_dir, ref, name): + '''Load two versions of a morphology and check for major conflicts + + Returns the version at 'ref' (if it exists) and the on-disk version. + + ''' + + new = self.load_morphology(repo_dir, name) + try: + old = self.load_morphology(repo_dir, name, ref=ref) + except cliapp.AppException as e: + return None, new + + if old['name'] != new['name']: + # We should enforce this in validation during load_morphology() + # rather than having to check it here + raise cliapp.AppException( + 'merge confict: "name" of morphology %s (name should ' + 'always match filename)' % name) + if old['kind'] != new['kind']: + raise cliapp.AppException( + 'merge conflict: "kind" of morphology %s' % name) + + return old, new + def merge_repo(self, name, from_dir, from_branch, to_dir, to_branch, commit = False): '''Merge changes for a system branch in a specific repository''' if self.get_uncommitted_changes(from_dir) != []: raise cliapp.AppException('repository %s has uncommitted ' - 'changes', name) + 'changes' % name) # repo must be made into a URL to avoid ':' in pathnames confusing git from_url = urlparse.urljoin('file://', from_dir) self.app.runcmd(['git', 'pull', '--no-commit', '--no-ff', from_url, @@ -624,32 +649,40 @@ class BranchAndMergePlugin(cliapp.Plugin): 'repository : %s vs %s' % (root_repo, other_root_repo)) - def _merge_chunk(ci): - from_repo = self.find_repository(from_branch_dir, ci['repo']) + def merge_chunk(old_ci, ci): + from_repo = self.find_repository(from_branch_dir, old_ci['repo']) to_repo = self.checkout_repository( - to_branch_dir, ci['repo'], to_branch) + to_branch_dir, ci['repo'], ci['ref']) self.merge_repo(ci['repo'], from_repo, from_branch, - to_repo, to_branch, commit=True) + to_repo, ci['ref'], commit=True) - def _merge_stratum(si): - if si['repo'] == root_repo: - to_repo = to_root_dir - else: - from_repo = self.find_repository(from_branch_dir, si['repo']) - to_repo = self.checkout_repository( - to_branch_dir, si['repo'], to_branch) - # We will do a merge commit in this repo later on + def merge_stratum(old_si, si): + from_repo = self.find_repository(from_branch_dir, old_si['repo']) + to_repo = self.checkout_repository( + to_branch_dir, si['repo'], si['ref']) + + if to_repo not in dirty_repos: self.merge_repo(si['repo'], from_repo, from_branch, - to_repo, to_branch, commit=False) - morphs_repo_list.add(to_repo) + to_repo, si['ref'], commit=False) + dirty_repos.add(to_repo) + old_stratum, stratum = self.load_morphology_pair( + to_repo, old_si['ref'], si['morph']) - stratum = self.load_morphology(to_repo, si['morph']) changed = False - for ci in stratum['chunks']: - if ci['ref'] == from_branch: - _merge_chunk(ci) - ci['ref'] = to_branch - changed = True + edited_chunks = [ci for ci in stratum['chunks'] + if ci['ref'] == from_branch] + for ci in edited_chunks: + for old_ci in old_stratum['chunks']: + if old_ci['repo'] == ci['repo']: + break + else: + raise cliapp.AppException( + 'chunk %s was added within this branch and ' + 'subsequently edited. This is not yet supported: ' + 'refusing to merge.' % ci['name']) + changed = True + ci['ref'] = old_ci['ref'] + merge_chunk(old_ci, ci) if changed: self.save_morphology(to_repo, si['morph'], stratum) @@ -658,26 +691,38 @@ class BranchAndMergePlugin(cliapp.Plugin): self.app.runcmd(['git', 'checkout', to_branch], cwd=to_root_dir) self.merge_repo(root_repo, from_root_dir, from_branch, to_root_dir, to_branch, commit=False) - morphs_repo_list = set([to_root_dir]) + dirty_repos = set([to_root_dir]) for f in glob.glob(os.path.join(to_root_dir, '*.morph')): - name = f[:-len('.morph')] - morphology = self.load_morphology(to_root_dir, name) + name = os.path.basename(f)[:-len('.morph')] + old_morphology, morphology = self.load_morphology_pair( + to_root_dir, to_branch, name) if morphology['kind'] == 'system': changed = False - for si in morphology['strata']: - if si['ref'] == from_branch: - _merge_stratum(si) - si['ref'] = to_branch - changed = True + edited_strata = [si for si in morphology['strata'] + if si['ref'] == from_branch] + for si in edited_strata: + for old_si in old_morphology['strata']: + # We make no attempt at rename / move detection + if old_si['morph'] == si['morph'] \ + and old_si['repo'] == si['repo']: + break + else: + raise cliapp.AppException( + 'stratum %s was added within this branch and ' + 'subsequently edited. This is not yet supported: ' + 'refusing to merge.' % si['morph']) + changed = True + si['ref'] = old_si['ref'] + merge_stratum(old_si, si) if changed: self.save_morphology(to_root_dir, name, morphology) - for repo in morphs_repo_list: + for repo_dir in dirty_repos: msg = "Merge system branch '%s'" % from_branch self.app.runcmd(['git', 'commit', '--all', '--message=%s' % msg], - cwd=repo) + cwd=repo_dir) def build(self, args): if len(args) != 1: |