summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2015-05-01 19:14:38 +0000
committerBaserock Gerrit <gerrit@baserock.org>2015-06-16 16:02:26 +0000
commit665ea01218dcba658d95b86014c7da5dd974a8a7 (patch)
treed9e5fa8598c5e64eaf6c609a109e3b889813dcb8
parent3f030c900570392180ce9bf813e546579e908eb3 (diff)
downloadmorph-665ea01218dcba658d95b86014c7da5dd974a8a7.tar.gz
Add DefinitionsRepo class
The intention is for this class to take over the from the Workspace and SystemBranch classes. It allows Morph to load and parse definitions from a Git repo, without requiring the user to run `morph checkout` or `morph branch`: it can operate from any normal Git repository. The class behaves differently when the Git repository is inside a Morph system-branch checkout made with `morph branch` or `morph checkout`, to avoid changing things under the feet of people who are used to those commands. Change-Id: I52a898efb9f6fb7f7e94c65b9ed38516bd51f49d
-rw-r--r--morphlib/__init__.py1
-rw-r--r--morphlib/buildbranch.py99
-rw-r--r--morphlib/definitions_repo.py415
-rw-r--r--morphlib/gitdir.py5
-rw-r--r--morphlib/sysbranchdir.py39
-rw-r--r--without-test-modules1
6 files changed, 521 insertions, 39 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py
index 94b42580..551e04cb 100644
--- a/morphlib/__init__.py
+++ b/morphlib/__init__.py
@@ -60,6 +60,7 @@ import builder
import cachedrepo
import cachekeycomputer
import cmdline_parse_utils
+import definitions_repo
import extensions
import extractedtarball
import fsutils
diff --git a/morphlib/buildbranch.py b/morphlib/buildbranch.py
index 2a2530b0..e16bf2b4 100644
--- a/morphlib/buildbranch.py
+++ b/morphlib/buildbranch.py
@@ -57,24 +57,70 @@ class BuildBranch(object):
# would be better to not use local repositories and temporary refs,
# so building from a workspace appears to be identical to using
# `morph build-morphology`
- def __init__(self, sb, build_ref_prefix):
-
- self._sb = sb
+ def __init__(self, build_ref_prefix, build_uuid, system_branch=None,
+ definitions_repo=None):
+ self._sb = system_branch
+ self._definitions_repo = definitions_repo
self._cleanup = collections.deque()
self._to_push = {}
self._td = fs.tempfs.TempFS()
self._register_cleanup(self._td.close)
- self._branch_root = sb.get_config('branch.root')
+ if system_branch:
+ # Old-style Morph system branch which may involve multiple repos.
+ #
+ # Temporary refs will look like this:
+ #
+ # $build-ref-prefix/$branch_uuid/$repo_uuid. For
+ #
+ # For example:
+ #
+ # baserock/builds/f0b21fe240b244edb7e4142b6e201658/8df11f234ab...
+ self._to_push = self.collect_repos_for_system_branch(
+ system_branch, build_ref_prefix)
+
+ branch_root = system_branch.get_config('branch.root')
+ self._root = system_branch.definitions_repo
+ _, self._root_index = self._to_push[self._root]
+ else:
+ # Temporary branch of only a definitions.git repo.
+ #
+ # Temporary ref will look like this:
+ #
+ # $build-ref-prefix/$HEAD/$build_uuid
+ #
+ # For example:
+ #
+ # baserock/builds/master/f0b21fe240b244edb7e4142b6e201658
+ ref = definitions_repo.HEAD
+ build_ref = os.path.join(
+ 'refs/heads', build_ref_prefix, ref, build_uuid)
+
+ index = definitions_repo.get_index(self._td.getsyspath('index'))
+ head_tree = definitions_repo.resolve_ref_to_tree(ref)
+ index.set_to_tree(head_tree)
+
+ self._to_push[definitions_repo] = (build_ref, index)
+ self._root, self._root_index = definitions_repo, index
+
+ def collect_repos_for_system_branch(self, sb, build_ref_prefix):
branch_uuid = sb.get_config('branch.uuid')
-
+ to_push = dict()
for count, gd in enumerate(sb.list_git_directories()):
try:
repo_uuid = gd.get_config('morph.uuid')
except cliapp.AppException:
# Not a repository cloned by morph, ignore
continue
+
+ if gd.dirname == sb.definitions_repo.dirname:
+ # Avoid creating a new GitDirectory instance for the
+ # definitions repo, which we already have a DefinitionsRepo
+ # instance for. This means that to_push[sb.definitions_repo]
+ # returns the expected results.
+ gd = sb.definitions_repo
+
build_ref = os.path.join('refs/heads', build_ref_prefix,
branch_uuid, repo_uuid)
# index is commit of workspace + uncommitted changes may want
@@ -82,15 +128,12 @@ class BuildBranch(object):
# so they can add new files first
index = gd.get_index(self._td.getsyspath(repo_uuid))
index.set_to_tree(gd.resolve_ref_to_tree(gd.HEAD))
- self._to_push[gd] = (build_ref, index)
+ to_push[gd] = (build_ref, index)
- if len(self._to_push) == 0:
+ if len(to_push) == 0:
raise NoReposError(self, count)
- rootinfo, = ((gd, index) for gd, (build_ref, index)
- in self._to_push.iteritems()
- if gd.get_config('morph.repository') == self._branch_root)
- self._root, self._root_index = rootinfo
+ return to_push
def _register_cleanup(self, func, *args, **kwargs):
self._cleanup.append((func, args, kwargs))
@@ -116,6 +159,12 @@ class BuildBranch(object):
sha1 = gd.store_blob(loader.save_to_string(morphology))
yield 0o100644, sha1, morphology.filename
+ def load_all_morphologies(self, loader):
+ if self._sb:
+ return self._sb.load_all_morphologies(loader)
+ else:
+ return self._root.load_all_morphologies(loader)
+
def inject_build_refs(self, loader, use_local_repos,
inject_cb=lambda **kwargs: None):
'''Update system and stratum morphologies to point to our branch.
@@ -128,15 +177,21 @@ class BuildBranch(object):
files into their in-memory representations and back again.
'''
- root_repo = self._root.get_config('morph.repository')
+ root_repo = self._root.remote_url
root_ref = self._root.HEAD
morphs = morphlib.morphset.MorphologySet()
- for morph in self._sb.load_all_morphologies(loader):
+
+ for morph in self.load_all_morphologies(loader):
morphs.add_morphology(morph)
sb_info = {}
for gd, (build_ref, index) in self._to_push.iteritems():
- repo, ref = gd.get_config('morph.repository'), gd.HEAD
+ if gd == self._root:
+ repo, ref = root_repo, root_ref
+ else:
+ # This branch can only run if we are in a Morph system branch
+ # checkout, because only there will we consider chunk repos.
+ repo, ref = gd.get_config('morph.repository'), gd.HEAD
sb_info[repo, ref] = (gd, build_ref)
def filter(m, kind, spec):
@@ -186,8 +241,10 @@ class BuildBranch(object):
4. commit message describing the current build using `uuid`
'''
- commit_message = 'Morph build %s\n\nSystem branch: %s\n' % \
- (uuid, self._sb.system_branch_name)
+ commit_message = 'Morph build %s\n' % uuid
+ if self._sb:
+ commit_message += "\nSystem branch: %s\n'" % \
+ self._sb.system_branch_name
author_name = name
committer_name = 'Morph (on behalf of %s)' % name
author_email = committer_email = email
@@ -258,11 +315,17 @@ class BuildBranch(object):
@property
def root_repo_url(self):
'''URI of the repository that systems may be found in.'''
- return self._sb.get_config('branch.root')
+ if self._sb:
+ return self._sb.get_config('branch.root')
+ else:
+ return self._definitions_repo.remote_url
@property
def root_ref(self):
- return self._sb.get_config('branch.name')
+ if self._sb:
+ return self._sb.get_config('branch.name')
+ else:
+ return self._definitions_repo.HEAD
@property
def root_commit(self):
diff --git a/morphlib/definitions_repo.py b/morphlib/definitions_repo.py
new file mode 100644
index 00000000..a7875148
--- /dev/null
+++ b/morphlib/definitions_repo.py
@@ -0,0 +1,415 @@
+# Copyright (C) 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/>.
+
+
+'''Handles the Git repository containing Baserock definitions.'''
+
+
+import cliapp
+
+import contextlib
+import logging
+import os
+import urlparse
+import uuid
+
+import morphlib
+import gitdir
+
+
+class DefinitionsRepoNotFound(cliapp.AppException):
+ def __init__(self):
+ cliapp.AppException.__init__(self,
+ 'This command must be run from inside a Git repository '
+ 'containing Baserock definitions.')
+
+
+class FileOutsideRepo(cliapp.AppException):
+ def __init__(self, path, repo):
+ cliapp.AppException.__init__(self,
+ 'File %s is not in repo %s.' % (path, repo))
+
+
+class DefinitionsRepo(gitdir.GitDirectory):
+ '''Represents a definitions.git repo checked out locally.
+
+ This can either be a normal Git clone, or a Git clone inside an old-style
+ Morph workspace.
+
+ If the repo is inside a Morph workspace, certain behaviours are enabled for
+ consistency with old versions of Morph. See function documentation for
+ details.
+
+ '''
+ def __init__(self, path, search_for_root=False, system_branch=None,
+ allow_missing=False):
+ morphlib.gitdir.GitDirectory.__init__(
+ self, path, search_for_root=search_for_root,
+ allow_missing=allow_missing)
+ self.system_branch = system_branch
+
+ @property
+ def HEAD(self):
+ '''Return the ref considered to be HEAD of this definitions repo.
+
+ In a normal Git checkout, this will return whatever ref is checked out
+ as the working tree (HEAD, in Git terminology).
+
+ If this definitions repo is in an old-style Morph system branch, it
+ will return the ref that was checked out with `morph branch` or `morph
+ checkout`, which will NOT necessarily correspond to what is checked out
+ in the Git repo.
+
+ '''
+ if self.system_branch is None:
+ return morphlib.gitdir.GitDirectory.HEAD.fget(self)
+ else:
+ return self.system_branch.get_config('branch.name')
+
+ @property
+ def remote_url(self):
+ '''Return the 'upstream' URL of this repo.
+
+ If this repo is inside a Morph system branch checkout, this will be
+ whatever URL was passed to `morph checkout` or `morph branch`. That may
+ be a keyed URL such as baserock:baserock/definitions.
+
+ Otherwise, the fetch URL of the 'origin' remote is returned.
+
+ '''
+ if self.system_branch is None:
+ return self.get_remote('origin').get_fetch_url()
+ else:
+ return self.system_branch.root_repository_url
+
+ def branch_with_local_changes(self, uuid, push=True, build_ref_prefix=None,
+ git_user_name=None, git_user_email=None,
+ status_cb=None):
+ '''Yield a branch that includes the user's local changes to this repo.
+
+ When operating on local repos, this isn't really necessary. But when
+ doing distributed building, any local changes the user has made need
+ to be pushed. As a convenience for the user, Morph supports creating
+ temporary branches with their local changes and pushing them to the
+ 'origin' remote of the repo they are working in.
+
+ If there are no local changes, there is no temporary branch created,
+ and the function yields whatever branch was checked out.
+
+ The 'git_user_name' and 'git_user_email' parameters are used when
+ creating commits in the temporary branch. The 'build_ref_prefix' is
+ prepended to the ref name of the temporary branch. Pushing is limited
+ to only certain refs in some Git servers.
+
+ '''
+ if status_cb:
+ status_cb(msg='Looking for uncommitted changes (pass '
+ '--local-changes=ignore to skip)')
+
+ if self.system_branch:
+ bb = morphlib.buildbranch.BuildBranch(
+ build_ref_prefix, uuid, system_branch=self.system_branch)
+ else:
+ bb = morphlib.buildbranch.BuildBranch(
+ build_ref_prefix, uuid, definitions_repo=self)
+
+ loader = morphlib.morphloader.MorphologyLoader()
+ pbb = morphlib.buildbranch.pushed_build_branch(
+ bb, loader=loader,
+ changes_need_pushing=push, name=git_user_name,
+ email=git_user_email, build_uuid=uuid,
+ status=status_cb)
+ return pbb # (repo_url, commit, original_ref)
+
+ @contextlib.contextmanager
+ def source_pool(self, lrc, rrc, cachedir, ref, system_filename,
+ include_local_changes=False, push_local_changes=False,
+ update_repos=True, status_cb=None, build_ref_prefix=None,
+ git_user_name=None, git_user_email=None):
+ '''Load the system defined in 'morph' and all the sources it contains.
+
+ This is a context manager, because depending on the settings given it
+ may create and push a temporary build branch. This is useful when there
+ are local changes that you would like distributed build workers to
+ build.
+
+ If 'include_local_changes' is False, the on-disk definitions.git repo
+ is used only to query the HEAD ref. Morph then looks for this ref in
+ its local clone of that repo's 'origin' remote, which ensures that the
+ changes it is building are pushed to the configured Git server (at
+ least at time it is building them).
+
+ When 'include_local_changes' is True, Morph will create a temporary
+ branch in the repo including any local changes. If the definitions.git
+ repo is inside an old-style Morph system branch, it will create
+ temporary branches in all repos that have been marked with `morph
+ edit`. The branch is cleaned up when the context manager exits. The
+ 'user_name', 'user_email' and 'build_ref_prefix' settings must be
+ passed if a temporary build branch is created.
+
+ FIXME: if not inside an old-style Morph system branch, the temporary
+ branch is redundant as Morph could just read the files from the disk
+ as-is. This requires changes to SourceResolver before it is possible.
+
+ The 'push_local_changes' option isn't much use. You probably want to
+ use branch_with_local_changes() instead. It is present so that the
+ `morph build` command continues to honour the 'push-build-branches'
+ setting, but that was probably only useful for `morph distbuild` and
+ that now uses branch_with_local_changes().
+
+ The 'lrc' and 'rrc' parameters are local and remote Git repo caches.
+ Use morphlib.util.new_repo_caches() to obtain these. The 'cachedir'
+ parameter points to where Git repos are cached by Morph,
+ app.settings['cachedir'] tells you that.
+
+ The 'update_repos' flag allows you to disable updating Git repos, to
+ honour app.settings['no-git-update']. If one of the refs in the build
+ graph is not available locally and update_repos is False, you will see
+ a morphlib.gitdir.InvalidRefError exception.
+
+ The 'status_cb' function will be called if set to output progress and
+ status messages to the user.
+
+ The function yields a morphlib.srcpool.SourcePool instance, which is
+ all you need to resolve cache keys, and construct a usable build graph.
+ See morphlib.buildcommand.BuildCommand.resolve_artifacts() for a way
+ of doing this.
+
+ '''
+ # FIXME: currently the way this function is implemented causes the
+ # `deploy` command to re-create a temporary build branch for each
+ # system that is deployed. This is a regression in terms of
+ # performance. But it seems to me that the sourcepool object should
+ # be able to contain multiple systems, and so the correct fix is to
+ # extend this function to handle multiple systems, rather than split
+ # up the 'process local changes' stage from the 'create source pool'
+ # stage.
+ if include_local_changes:
+ build_uuid = uuid.uuid4().hex
+ temporary_branch = DefinitionsRepo.branch_with_local_changes(
+ self, build_uuid, push=push_local_changes,
+ build_ref_prefix=build_ref_prefix, git_user_name=git_user_name,
+ git_user_email=git_user_email, status_cb=status_cb)
+ with temporary_branch as (repo_url, commit, original_ref):
+ yield morphlib.sourceresolver.create_source_pool(
+ lrc, rrc, repo_url, commit, [system_filename],
+ cachedir=cachedir, original_ref=original_ref,
+ update_repos=update_repos, status_cb=status_cb)
+ else:
+ if self.system_branch:
+ repo_url = self.systembranch.root_repository
+ else:
+ repo_url = self.get_remote('origin')
+ commit = self.resolve_ref_to_commit(ref)
+
+ if status_cb:
+ status_cb(msg='Deciding on task order')
+
+ yield morphlib.sourceresolver.create_source_pool(
+ lrc, rrc, repo_url, commit, [system_filename],
+ cachedir=cachedir, original_ref=ref, update_repos=update_repos,
+ status_cb=status_cb)
+
+ def load_all_morphologies(self, loader):
+ mf = morphlib.morphologyfinder.MorphologyFinder(self)
+ for filename in (f for f in mf.list_morphologies()
+ if not self.is_symlink(f)):
+ text = mf.read_morphology(filename)
+ m = loader.load_from_string(text, filename=filename)
+ m.repo_url = self.remote_url
+ m.ref = self.HEAD
+ yield m
+
+ def relative_path(self, path, cwd='.'):
+ '''Make 'path' relative to the top directory of this repo.
+
+ If 'path' is a relative path, it is taken to be relative to the
+ current working directory. Thus, the result of this function will
+ be different depending on the value of os.getcwd().
+
+ If the given path is outside the repo, a PathOutsideRepo exception
+ is raised.
+
+ '''
+ def path_is_outside_repo(path):
+ return path.split(os.sep, 1)[0] == '..'
+
+ absolute_path = os.path.abspath(path)
+ repo_relative_path = os.path.relpath(absolute_path, self.dirname)
+
+ if path_is_outside_repo(repo_relative_path):
+ raise FileOutsideRepo(repo_relative_path, self)
+
+ return repo_relative_path
+
+ def relative_path_to_chunk(self, repo_url):
+ '''Return a sensible directory to check out repo_url.
+
+ This will be a path in the directory that contains this definitions
+ repo, with its name based on 'repo_url'.
+
+ '''
+ # This is copied from systembranch._fabricate_git_directory_name().
+
+ # Parse the URL. If the path component is absolute, we assume
+ # it's a real URL; otherwise, an aliased URL.
+ parts = urlparse.urlparse(repo_url)
+
+ if os.path.isabs(parts.path):
+ # Remove .git suffix, if any.
+ path = parts.path
+ if path.endswith('.git'):
+ path = path[:-len('.git')]
+
+ # Add the domain name etc (netloc). Ignore any other parts.
+ # Note that we _know_ the path starts with a slash, so we avoid
+ # adding one here.
+ relative = '%s%s' % (parts.netloc, path)
+ else:
+ relative = repo_url
+
+ # Replace colons with slashes.
+ relative = '/'.join(relative.split(':'))
+
+ # Remove anyleading slashes, or os.path.join below will only
+ # use the relative part (since it's absolute, not relative).
+ relative = relative.lstrip('/')
+
+ return os.path.join(os.path.dirname(self.dirname), relative)
+
+
+class DefinitionsRepoWithApp(DefinitionsRepo):
+ '''Wrapper class for DefinitionsRepo that understands Morph settings.
+
+ The DefinitionsRepo class does not require a morphlib.app.Application
+ instance to use it. However, this means you need to pass quite a lot
+ of parameters in. Code inside Morph can use this class instead to save
+ duplicating code.
+
+ '''
+ def __init__(self, app, *args, **kwargs):
+ DefinitionsRepo.__init__(self, *args, **kwargs)
+ self.app = app
+
+ self._git_user_name = morphlib.git.get_user_name(app.runcmd)
+ self._git_user_email = morphlib.git.get_user_email(app.runcmd)
+
+ self._lrc, self._rrc = morphlib.util.new_repo_caches(app)
+
+ def branch_with_local_changes(self, uuid, push=False):
+ '''Equivalent to DefinitionsRepo.branch_with_local_changes().'''
+
+ return DefinitionsRepo.branch_with_local_changes(
+ self, uuid,
+ push=(push or self.app.settings['push-build-branches']),
+ build_ref_prefix=self.app.settings['build-ref-prefix'],
+ git_user_name=self._git_user_name,
+ git_user_email=self._git_user_email,
+ status_cb=self.app.status,)
+
+ def source_pool(self, ref, system_filename,
+ include_local_changes=False):
+ '''Equivalent to DefinitionsRepo.source_pool().'''
+
+ return DefinitionsRepo.source_pool(
+ self, self._lrc, self._rrc, self.app.settings['cachedir'],
+ ref, system_filename,
+ include_local_changes=include_local_changes,
+ push_local_changes=self.app.settings['push-build-branches'],
+ build_ref_prefix=self.app.settings['build-ref-prefix'],
+ git_user_name=self._git_user_name,
+ git_user_email=self._git_user_email,
+ status_cb=self.app.status,
+ update_repos=(not self.app.settings['no-git-update']))
+
+
+def _system_branch(path):
+ '''Open an old-style Morph system branch in an old-style Morph workspace.
+
+ Raises morphlib.workspace.NotInWorkspace or
+ morphlib.sysbranchdir.NotInSystemBranch if either workspace or
+ system-branch are not found.
+
+ '''
+ morphlib.workspace.open(path)
+ system_branch = morphlib.sysbranchdir.open_from_within(path)
+ return system_branch
+
+
+def _local_definitions_repo(path, search_for_root, system_branch=None,
+ app=None):
+ '''Open a local Git repo containing Baserock definitions, at 'path'.
+
+ Raises morphlib.gitdir.NoGitRepoError if there is no repo found at 'path'.
+
+ '''
+ if app:
+ gitdir = morphlib.definitions_repo.DefinitionsRepoWithApp(
+ app, path, search_for_root=search_for_root,
+ system_branch=system_branch)
+ else:
+ gitdir = morphlib.definitions_repo.DefinitionsRepo(
+ path, search_for_root=search_for_root, system_branch=system_branch)
+ return gitdir
+
+
+def open(path, search_for_root=False, search_workspace=False, app=None):
+ '''Open the definitions.git repo at 'path'.
+
+ Returns a DefinitionsRepo instance.
+
+ If 'search_for_root' is True, this function will traverse up from 'path'
+ to find a .git directory, and assume that is the top of the Git repository.
+ If you are trying to find the repo based on the current working directory,
+ you should set this to True. If you are trying to find the repo based on a
+ path entered manually by the user, you may want to set this to False to
+ avoid confusion.
+
+ If 'search_workspace' is True, this function will check if 'path' is inside
+ an old-style Morph workspace. If it is, there will be two changes to its
+ behaviour. First, the definitions.git will be returned even if 'path' is
+ inside a different repo, because the old-style Morph system branch will
+ identify which is the correct definitions.git repo. Second, the value
+ returned for HEAD will not be the ref checked out in the definitions.git
+ repo, but rather the ref that was passed to `morph checkout` or `morph
+ branch` when the system branch was originally checked out. This behaviour
+ may seem confusing if you are new to Morph, but in fact Morph forced users
+ to work this way for several years, so we need preserve this behaviour for
+ a while to avoid disrupting existing users.
+
+ '''
+ sb = None
+
+ if search_workspace:
+ try:
+ sb = _system_branch(path)
+ except (morphlib.workspace.NotInWorkspace,
+ morphlib.sysbranchdir.NotInSystemBranch):
+ logging.debug('Did not find old-style Morph system branch')
+
+ if sb:
+ path = sb.get_git_directory_name(sb.root_repository_url)
+ definitions_repo = _local_definitions_repo(
+ path=path, search_for_root=False, system_branch=sb, app=app)
+ logging.info('Opened definitions repo %s from Morph system branch %s',
+ definitions_repo, sb)
+ else:
+ try:
+ definitions_repo = _local_definitions_repo(
+ path, search_for_root=search_for_root, app=app)
+ except morphlib.gitdir.NoGitRepoError:
+ raise DefinitionsRepoNotFound()
+ logging.info('Opened definitions repo %s', definitions_repo)
+
+ return definitions_repo
diff --git a/morphlib/gitdir.py b/morphlib/gitdir.py
index 9cb62e3b..8716da16 100644
--- a/morphlib/gitdir.py
+++ b/morphlib/gitdir.py
@@ -425,7 +425,7 @@ class GitDirectory(object):
'''
- def __init__(self, dirname, search_for_root=False):
+ def __init__(self, dirname, search_for_root=False, allow_missing=False):
'''Set up a GitDirectory instance for the repository at 'dirname'.
If 'search_for_root' is set to True, 'dirname' may point to a
@@ -440,7 +440,8 @@ class GitDirectory(object):
self.dirname = dirname
self.config = Config(config_file=None, runcmd=self._runcmd)
- self._ensure_is_git_repo()
+ if not allow_missing:
+ self._ensure_is_git_repo()
def __str__(self):
return self.dirname
diff --git a/morphlib/sysbranchdir.py b/morphlib/sysbranchdir.py
index 3ca5a8cb..d23d9eea 100644
--- a/morphlib/sysbranchdir.py
+++ b/morphlib/sysbranchdir.py
@@ -46,12 +46,28 @@ class SystemBranchDirectory(object):
def __init__(self,
root_directory, root_repository_url, system_branch_name):
- self.root_directory = os.path.abspath(root_directory)
- self.root_repository_url = root_repository_url
self.system_branch_name = system_branch_name
+ # The 'root repo' is the definitions repo. This attribute is named this
+ # way for historical reasons.
+ self.root_repository_url = root_repository_url
+ assert root_repository_url is not None
+
+ # The 'root directory' is the parent directory of all checked-out
+ # repos. For example, the root directory of branch 'sam/test' in
+ # workspace '/src/ws' would be '/src/ws/sam/test' (at the time it was
+ # created by `morph branch` or `morph checkout`, anyway).
+ self.root_directory = os.path.abspath(root_directory)
+
self.config = morphlib.gitdir.Config(config_file=self._config_path)
+ # In order to handle newly-created system branch directories (and for
+ # the unit tests) we don't raise an error here if the definitions repo
+ # isn't cloned yet. Some methods won't work if it doesn't exist though.
+ definitions_repo_dir = self.get_git_directory_name(root_repository_url)
+ self.definitions_repo = morphlib.definitions_repo.DefinitionsRepo(
+ definitions_repo_dir, system_branch=self, allow_missing=True)
+
@property
def _magic_path(self):
return os.path.join(self.root_directory, '.morph-system-branch')
@@ -106,9 +122,7 @@ class SystemBranchDirectory(object):
return os.path.join(self.root_directory, relative)
def relative_to_root_repo(self, path): # pragma: no cover
- gitdirpath = self.get_git_directory_name(self.root_repository_url)
-
- return os.path.relpath(os.path.abspath(path), gitdirpath)
+ return self.definitions_repo.relative_path(path)
def get_git_directory_name(self, repo_url):
'''Return directory pathname for a given git repository.
@@ -193,21 +207,8 @@ class SystemBranchDirectory(object):
for dirname in
morphlib.util.find_leaves(self.root_directory, '.git'))
- # Not covered by unit tests, since testing the functionality spans
- # multiple modules and only tests useful output with a full system
- # branch, so it is instead covered by integration tests.
def load_all_morphologies(self, loader): # pragma: no cover
- gd_name = self.get_git_directory_name(self.root_repository_url)
- gd = morphlib.gitdir.GitDirectory(gd_name)
- mf = morphlib.morphologyfinder.MorphologyFinder(gd)
- for filename in (f for f in mf.list_morphologies()
- if not gd.is_symlink(f)):
- text = mf.read_morphology(filename)
- m = loader.load_from_string(text, filename=filename)
- m.repo_url = self.root_repository_url
- m.ref = self.system_branch_name
- yield m
-
+ return self.definitions_repo.load_all_morphologies(loader)
def create(root_directory, root_repository_url, system_branch_name):
diff --git a/without-test-modules b/without-test-modules
index df1aa2dd..95f5c13e 100644
--- a/without-test-modules
+++ b/without-test-modules
@@ -61,3 +61,4 @@ distbuild/timer_event_source.py
distbuild/worker_build_scheduler.py
# Not unit tested, since it needs a full system branch
morphlib/buildbranch.py
+morphlib/definitions_repo.py