summaryrefslogtreecommitdiff
path: root/morphlib/plugins
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2012-09-12 17:07:02 +0100
committerSam Thursfield <sam.thursfield@codethink.co.uk>2012-09-14 19:44:40 +0100
commitf7a3f591a12baa17fa0380ba36405651fe2b0126 (patch)
tree6b4cd50ff096b3cc28b153bf86d53489a529de4a /morphlib/plugins
parentd3d278b6bdade8141836bcd35a47d7476ae49ecc (diff)
downloadmorph-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.py107
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: