summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-01-19 15:32:58 +0000
committerJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-01-20 18:31:51 +0000
commitc73ca82e0c67ba3d05f47f61766f8838d0e9d8d4 (patch)
treefb3fb2f063af4167da1c287d67e4ae2d818e7748
parent55751d6de5927c3bbcdd21321f7c3a6655e87a76 (diff)
downloadmorph-c73ca82e0c67ba3d05f47f61766f8838d0e9d8d4.tar.gz
Port everything to using Treeish objects instead of (repo, ref).
This affects pretty much every part of morph, so this might not be fully working and stable yet. This commit also introduces the "update-gits" command that can be used to update all cached repositories from the list of base URLs. The tree walk when resolving the Treeish objects in Builder.get_cache_id() is a bit similar to what we do in BuildDependencyGraph, maybe we can merge that one day.
-rwxr-xr-xmorph61
-rw-r--r--morphlib/blobs.py15
-rw-r--r--morphlib/blobs_tests.py62
-rw-r--r--morphlib/builddependencygraph.py66
-rw-r--r--morphlib/builder.py73
-rw-r--r--morphlib/git.py67
-rw-r--r--morphlib/morphology.py37
-rw-r--r--morphlib/morphology_tests.py82
-rw-r--r--morphlib/morphologyloader.py35
-rw-r--r--morphlib/morphologyloader_tests.py11
-rw-r--r--morphlib/sourcemanager.py23
-rw-r--r--morphlib/sourcemanager_tests.py46
-rw-r--r--tests/missing-ref.stderr2
-rw-r--r--tests/show-dependencies.stdout244
14 files changed, 438 insertions, 386 deletions
diff --git a/morph b/morph
index 86a71ac5..4210d59d 100755
--- a/morph
+++ b/morph
@@ -3,7 +3,7 @@
# WARNING: THIS IS HIGHLY EXPERIMENTAL CODE RIGHT NOW. JUST PROOF OF CONCEPT.
# DO NOT RUN UNTIL YOU KNOW WHAT YOU ARE DOING.
#
-# Copyright (C) 2011 Codethink Limited
+# Copyright (C) 2011-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
@@ -25,6 +25,7 @@ import os
import urlparse
import morphlib
+from morphlib.morphologyloader import MorphologyLoader
from morphlib.builddependencygraph import BuildDependencyGraph
@@ -79,8 +80,10 @@ class Morph(cliapp.Application):
'''
tempdir = morphlib.tempdir.Tempdir()
- loader = morphlib.morphologyloader.MorphologyLoader(self.settings)
- builder = morphlib.builder.Builder(tempdir, self, loader)
+ morph_loader = MorphologyLoader(self.settings)
+ source_manager = morphlib.sourcemanager.SourceManager(self)
+ builder = morphlib.builder.Builder(tempdir, self, morph_loader,
+ source_manager)
if not os.path.exists(self.settings['cachedir']):
os.mkdir(self.settings['cachedir'])
@@ -90,21 +93,13 @@ class Morph(cliapp.Application):
repo, ref, filename = args[:3]
args = args[3:]
- # resolve the URL to the repository
- base_url = self.settings['git-base-url']
- if not base_url.endswith('/'):
- base_url += '/'
- repo = urlparse.urljoin(base_url, repo)
- if not repo.endswith('/'):
- repo += '/'
-
# derive a build order from the dependency graph
- morphology = loader.load(repo, ref, filename)
- graph = BuildDependencyGraph(loader, morphology)
+ graph = BuildDependencyGraph(source_manager, morph_loader,
+ repo, ref, filename)
graph.resolve()
blobs, order = graph.build_order()
- self.msg('Building %s' % morphology)
+ self.msg('Building %s|%s|%s' % (repo, ref, filename))
# build things in this order
ret.append(builder.build(blobs, order))
@@ -172,25 +167,17 @@ class Morph(cliapp.Application):
def cmd_show_dependencies(self, args):
'''Dumps the dependency tree of all input morphologies.'''
+ morph_loader = MorphologyLoader(self.settings)
+ source_manager = morphlib.sourcemanager.SourceManager(self)
+
while len(args) >= 3:
# read the build tuple from the command line
repo, ref, filename = args[:3]
args = args[3:]
- # resolve the URL to the repository
- base_url = self.settings['git-base-url']
- if not base_url.endswith('/'):
- base_url += '/'
- repo = urlparse.urljoin(base_url, repo)
- if not repo.endswith('/'):
- repo += '/'
-
- # load the morphology corresponding to the build tuple
- loader = morphlib.morphologyloader.MorphologyLoader(self.settings)
- morphology = loader.load(repo, ref, filename)
-
# create a dependency graph for the morphology
- graph = BuildDependencyGraph(loader, morphology)
+ graph = BuildDependencyGraph(source_manager, morph_loader,
+ repo, ref, filename)
graph.resolve()
# print the graph
@@ -208,6 +195,26 @@ class Morph(cliapp.Application):
for blob in sorted(group, key=str):
self.output.write(' %s\n' % blob)
+ def cmd_update_gits(self, args):
+ tempdir = morphlib.tempdir.Tempdir()
+ morph_loader = MorphologyLoader(self.settings)
+ source_manager = morphlib.sourcemanager.SourceManager(self)
+ while len(args) >= 3:
+ # read the build tuple from the command line
+ repo, ref, filename = args[:3]
+ args = args[3:]
+
+ # first step: clone the corresponding repo
+ treeish = source_manager.get_treeish(repo, ref)
+ morph = morph_loader.load(treeish, filename)
+ blob = morphlib.blobs.Blob.create_blob(morph)
+
+ # second step: compute the cache ID, which will implicitly
+ # clone all repositories needed to build the blob
+ builder = morphlib.builder.Builder(tempdir, self, morph_loader,
+ source_manager)
+ builder.get_cache_id(blob)
+
def msg(self, msg):
'''Show a message to the user about what is going on.'''
logging.debug(msg)
diff --git a/morphlib/blobs.py b/morphlib/blobs.py
index 1732f000..d02643a6 100644
--- a/morphlib/blobs.py
+++ b/morphlib/blobs.py
@@ -14,8 +14,23 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import os
+
+
class Blob(object):
+ @staticmethod
+ def create_blob(morph):
+ if morph.kind == 'stratum':
+ return Stratum(morph)
+ elif morph.kind == 'chunk':
+ return Chunk(morph)
+ elif morph.kind == 'system':
+ return System(morph)
+ else:
+ raise TypeError('Morphology %s has an unknown type: %s' %
+ (morph.filename, morph.kind))
+
def __init__(self, morph):
self.parents = []
self.morph = morph
diff --git a/morphlib/blobs_tests.py b/morphlib/blobs_tests.py
index c012206a..f92658ee 100644
--- a/morphlib/blobs_tests.py
+++ b/morphlib/blobs_tests.py
@@ -21,6 +21,52 @@ import morphlib
class BlobsTests(unittest.TestCase):
+ def test_create_a_chunk_blob(self):
+ class FakeChunkMorph(object):
+ @property
+ def kind(self):
+ return 'chunk'
+
+ morph = FakeChunkMorph()
+ chunk = morphlib.blobs.Blob.create_blob(morph)
+ self.assertTrue(isinstance(chunk, morphlib.blobs.Chunk))
+ self.assertEqual(morph, chunk.morph)
+
+ def test_create_a_stratum_blob(self):
+ class FakeStratumMorph(object):
+ @property
+ def kind(self):
+ return 'stratum'
+
+ morph = FakeStratumMorph()
+ stratum = morphlib.blobs.Blob.create_blob(morph)
+ self.assertTrue(isinstance(stratum, morphlib.blobs.Stratum))
+ self.assertEqual(morph, stratum.morph)
+
+ def test_create_a_system_blob(self):
+ class FakeSystemMorph(object):
+ @property
+ def kind(self):
+ return 'system'
+
+ morph = FakeSystemMorph()
+ system = morphlib.blobs.Blob.create_blob(morph)
+ self.assertTrue(isinstance(system, morphlib.blobs.System))
+ self.assertEqual(morph, system.morph)
+
+ def test_create_an_invalid_blob(self):
+ class FakeInvalidMorph(object):
+ @property
+ def kind(self):
+ return 'invalid'
+
+ @property
+ def filename(self):
+ return '/foo/bar/baz.morph'
+
+ morph = FakeInvalidMorph()
+ self.assertRaises(TypeError, morphlib.blobs.Blob.create_blob, morph)
+
def test_blob_with_parents(self):
blob1 = morphlib.blobs.Blob(None)
blob2 = morphlib.blobs.Blob(None)
@@ -93,17 +139,23 @@ class BlobsTests(unittest.TestCase):
self.assertFalse(blob2.depends_on(blob1))
def test_chunks(self):
- settings = { 'git-base-url': '' }
+ settings = { 'git-base-url': [] }
loader = morphlib.morphologyloader.MorphologyLoader(settings)
loader._get_morph_text = self.get_morph_text
-
- stratum_morph = loader.load('repo', 'ref', 'foo.morph')
+
+ class FakeTreeish(object):
+ def __init__(self):
+ self.original_repo = 'test-repo'
+ faketreeish = FakeTreeish()
+
+ faketreeish.original_repo = 'hello'
+ stratum_morph = loader.load(faketreeish, 'foo.morph')
stratum = morphlib.blobs.Stratum(stratum_morph)
self.assertEquals(len(stratum.chunks), 1)
self.assertTrue('foo' in stratum.chunks)
self.assertEqual(['.'], stratum.chunks['foo'])
- chunk_morph = loader.load('repo', 'ref', 'bar.morph')
+ chunk_morph = loader.load(faketreeish, 'bar.morph')
chunk = morphlib.blobs.Chunk(chunk_morph)
self.assertEqual(len(chunk.chunks), 2)
self.assertTrue('include' in chunk.chunks)
@@ -111,7 +163,7 @@ class BlobsTests(unittest.TestCase):
self.assertTrue('src' in chunk.chunks)
self.assertEqual(chunk.chunks['src'], ['src/'])
- def get_morph_text(self, repo, ref, filename):
+ def get_morph_text(self, treeish, filename):
if filename == 'foo.morph':
return ('''
{
diff --git a/morphlib/builddependencygraph.py b/morphlib/builddependencygraph.py
index a280abbd..c0f2b27c 100644
--- a/morphlib/builddependencygraph.py
+++ b/morphlib/builddependencygraph.py
@@ -31,14 +31,19 @@ class BuildDependencyGraph(object): # pragma: no cover
'''
- def __init__(self, loader, morph):
- self.loader = loader
- self.morph = morph
+ def __init__(self, source_manager, morph_loader, repo, ref, filename):
+ self.source_manager = source_manager
+ self.morph_loader = morph_loader
+ self.root_repo = repo
+ self.root_ref = ref
+ self.root_filename = filename
self.blobs = set()
- def create_blob(self, morph):
+ def create_blob(self, treeish, filename):
'''Creates a blob from a morphology.'''
+ morph = self.morph_loader.load(treeish, filename)
+
if morph.kind == 'stratum':
return morphlib.blobs.Stratum(morph)
elif morph.kind == 'chunk':
@@ -46,19 +51,19 @@ class BuildDependencyGraph(object): # pragma: no cover
else:
return morphlib.blobs.System(morph)
- def get_blob(self, info):
- '''Takes a (repo, ref, filename) tuple and looks up the blob for it.
+ def get_blob(self, treeish, filename):
+ '''Takes a repo, ref, filename and looks up the blob for them.
Loads the corresponding morphology and chunk/stratum/system object
on-demand if it is not cached yet.
'''
- blob = self.cached_blobs.get(info, None)
+ key = (treeish, filename)
+ blob = self.cached_blobs.get(key, None)
if not blob:
- morphology = self.loader.load(info[0], info[1], info[2])
- blob = self.create_blob(morphology)
- self.cached_blobs[info] = blob
+ blob = self.create_blob(treeish, filename)
+ self.cached_blobs[key] = blob
return blob
def resolve(self):
@@ -72,17 +77,17 @@ class BuildDependencyGraph(object): # pragma: no cover
self.resolve_chunks()
def resolve_root(self):
- # convert the morphology to a chunk/stratum/system object
- root = self.create_blob(self.morph)
+ # prepare the repo, load the morphology and blob information
+ treeish = self.source_manager.get_treeish(self.root_repo,
+ self.root_ref)
+ root = self.get_blob(treeish, self.root_filename)
self.blobs.add(root)
# load all strata the morph depends on (only if it's a system image)
if root.morph.kind == 'system':
for stratum_name in root.morph.strata:
- info = (root.morph.repo,
- root.morph.ref,
- '%s.morph' % stratum_name)
- stratum = self.get_blob(info)
+ filename = '%s.morph' % stratum_name
+ stratum = self.get_blob(treeish, filename)
root.add_dependency(stratum)
stratum.add_parent(root)
self.blobs.add(stratum)
@@ -112,14 +117,9 @@ class BuildDependencyGraph(object): # pragma: no cover
# verify that the build-depends format is valid
if isinstance(stratum.morph.build_depends, list):
for depname in stratum.morph.build_depends:
- # prepare a tuple for the dependency stratum
- repo = stratum.morph.repo
- ref = stratum.morph.ref
- filename = '%s.morph' % depname
- info = (repo, ref, filename)
-
# load the dependency stratum on demand
- depstratum = self.get_blob(info)
+ depstratum = self.get_blob(stratum.morph.treeish,
+ '%s.morph' % depname)
self.blobs.add(depstratum)
# add the dependency stratum to the graph
@@ -138,10 +138,6 @@ class BuildDependencyGraph(object): # pragma: no cover
'''
- if self.morph.kind == 'chunk':
- blob = self.create_blob(self.morph)
- self.blobs.add(blob)
-
blobs = list(self.blobs)
for blob in blobs:
if isinstance(blob, morphlib.blobs.Stratum):
@@ -164,10 +160,10 @@ class BuildDependencyGraph(object): # pragma: no cover
filename = '%s.morph' % (source['morph']
if 'morph' in source
else source['name'])
- info = (repo, ref, filename)
# load the chunk on demand
- chunk = self.get_blob(info)
+ treeish = self.source_manager.get_treeish(repo, ref)
+ chunk = self.get_blob(treeish, filename)
chunk.add_parent(stratum)
# store (name -> chunk) association to avoid loading the chunk twice
@@ -190,15 +186,14 @@ class BuildDependencyGraph(object): # pragma: no cover
dependency = name_to_chunk[depname]
chunk.add_dependency(dependency)
else:
- filename = os.path.basename(stratum.morph.filename)
raise Exception('%s: source %s references %s before it '
- 'is defined' % (filename,
+ 'is defined' % (stratum.morph.filename,
source['name'],
depname))
else:
- filename = os.path.basename(stratum.morph.filename)
raise Exception('%s: source %s uses an invalid build-depends '
- 'format' % (filename, source['name']))
+ 'format' %
+ (stratum.morph.filename, source['name']))
# add the chunk to stratum and graph
stratum_chunks.add(chunk)
@@ -304,6 +299,9 @@ class BuildDependencyGraph(object): # pragma: no cover
# have found at least one cyclic dependency
if len(sorting) < len(self.blobs):
raise Exception('Cyclic dependencies found in the dependency '
- 'graph of "%s"' % self.morph)
+ 'graph of %s|%s|%s' %
+ (self.root_repo,
+ self.root_ref,
+ self.root_filename))
return sorting
diff --git a/morphlib/builder.py b/morphlib/builder.py
index d1585953..33aa5cca 100644
--- a/morphlib/builder.py
+++ b/morphlib/builder.py
@@ -286,10 +286,7 @@ class ChunkBuilder(BlobBuilder):
self.dump_memory_profile('before creating source and tarball '
'for chunk')
tarball = self.cache_prefix + '.src.tar'
- #FIXME Ugh use treeish everwhere
- path = urlparse.urlparse(self.blob.morph.repo).path
- t = morphlib.git.Treeish(path, self.blob.morph.ref)
- morphlib.git.export_sources(t, tarball)
+ morphlib.git.export_sources(self.blob.morph.treeish, tarball)
self.dump_memory_profile('after exporting sources')
os.mkdir(self.builddir)
self.ex.runv(['tar', '-C', self.builddir, '-xf', tarball])
@@ -490,14 +487,15 @@ class Builder(object):
The objects may be chunks or strata.'''
- def __init__(self, tempdir, app, morph_loader):
+ def __init__(self, tempdir, app, morph_loader, source_manager):
self.tempdir = tempdir
self.real_msg = app.msg
self.settings = app.settings
self.dump_memory_profile = app.dump_memory_profile
self.cachedir = morphlib.cachedir.CacheDir(self.settings['cachedir'])
- self.indent = 0
self.morph_loader = morph_loader
+ self.source_manager = source_manager
+ self.indent = 0
def msg(self, text):
spaces = ' ' * self.indent
@@ -569,7 +567,7 @@ class Builder(object):
raise TypeError('Blob %s has unknown type %s' %
(str(blob), type(blob)))
- cache_id = self.get_blob_cache_id(blob)
+ cache_id = self.get_cache_id(blob)
logging.debug('cache id: %s' % repr(cache_id))
self.dump_memory_profile('after computing cache id')
@@ -584,44 +582,41 @@ class Builder(object):
return builder
- def get_blob_cache_id(self, blob):
- # FIXME os.path.basename() only works if the .morph file is an
- # immediate children of the repo location and is not located in
- # a subfolder
- return self.get_cache_id(blob.morph.repo,
- blob.morph.ref,
- os.path.basename(blob.morph.filename))
-
- def get_cache_id(self, repo, ref, morph_filename):
- logging.debug('get_cache_id(%s, %s, %s)' %
- (repo, ref, morph_filename))
- morph = self.morph_loader.load(repo, ref, morph_filename)
- if morph.kind == 'chunk':
+ def get_cache_id(self, blob):
+ logging.debug('get_cache_id(%s)' % blob)
+
+ if blob.morph.kind == 'chunk':
kids = []
- elif morph.kind == 'stratum':
+ elif blob.morph.kind == 'stratum':
kids = []
- for source in morph.sources:
- kid_repo = source['repo']
- kid_ref = source['ref']
- kid_filename = (source['morph']
- if 'morph' in source
- else source['name'])
- kid_filename = '%s.morph' % kid_filename
- kid_cache_id = self.get_cache_id(kid_repo, kid_ref,
- kid_filename)
- kids.append(kid_cache_id)
- elif morph.kind == 'system':
+ for source in blob.morph.sources:
+ repo = source['repo']
+ ref = source['ref']
+ treeish = self.source_manager.get_treeish(repo, ref)
+ filename = (source['morph']
+ if 'morph' in source
+ else source['name'])
+ filename = '%s.morph' % filename
+ morph = self.morph_loader.load(treeish, filename)
+ chunk = morphlib.blobs.Blob.create_blob(morph)
+ cache_id = self.get_cache_id(chunk)
+ kids.append(cache_id)
+ elif blob.morph.kind == 'system':
kids = []
- for stratum in morph.strata:
- kid_filename = '%s.morph' % stratum
- kid_cache_id = self.get_cache_id(repo, ref, kid_filename)
- kids.append(kid_cache_id)
+ for stratum_name in blob.morph.strata:
+ filename = '%s.morph' % stratum_name
+ morph = self.morph_loader.load(blob.morph.treeish, filename)
+ stratum = morphlib.blobs.Blob.create_blob(morph)
+ cache_id = self.get_cache_id(stratum)
+ kids.append(cache_id)
else:
- raise NotImplementedError('unknown morph kind %s' % morph.kind)
+ raise NotImplementedError('unknown morph kind %s' %
+ blob.morph.kind)
+
dict_key = {
- 'name': morph.name,
+ 'name': blob.morph.name,
'arch': morphlib.util.arch(),
- 'ref': morphlib.git.get_commit_id(repo, ref),
+ 'ref': blob.morph.treeish.sha1,
'kids': ''.join(self.cachedir.key(k) for k in kids),
}
return dict_key
diff --git a/morphlib/git.py b/morphlib/git.py
index 2cde8c55..36cb726d 100644
--- a/morphlib/git.py
+++ b/morphlib/git.py
@@ -25,47 +25,60 @@ import cliapp
class NoMorphs(Exception):
def __init__(self, repo, ref):
- Exception.__init__(self,
- 'Cannot find any morpologies at %s:%s' %
- (repo, ref))
+ Exception.__init__(self, 'Cannot find any morpologies at %s:%s' %
+ (repo, ref))
class TooManyMorphs(Exception):
def __init__(self, repo, ref, morphs):
- Exception.__init__(self,
- 'Too many morphologies at %s:%s: %s' %
- (repo, ref, ', '.join(morphs)))
+ Exception.__init__(self, 'Too many morphologies at %s:%s: %s' %
+ (repo, ref, ', '.join(morphs)))
+
class InvalidTreeish(cliapp.AppException):
def __init__(self, repo, ref):
- Exception.__init__(self,
- '%s is an invalid reference for repo %s' %
- (ref,repo))
+ Exception.__init__(self, '%s is an invalid reference for repo %s' %
+ (ref, repo))
+
+class Treeish(object):
-class Treeish:
- def __init__(self, repo, ref, msg=logging.debug):
+ def __init__(self, repo, original_repo, ref, msg=logging.debug):
self.repo = repo
self.msg = msg
self.sha1 = None
self.ref = None
+ self.original_repo = original_repo
self._resolve_ref(ref)
-
+
+ def __hash__(self):
+ return hash((self.repo, self.ref))
+
+ def __eq__(self, other):
+ return other.repo == self.repo and other.ref == self.ref
+
+ def __str__(self):
+ return '%s:%s' % (self.repo, self.ref)
+
def _resolve_ref(self, ref):
ex = morphlib.execute.Execute(self.repo, self.msg)
try:
- refs = ex.runv(['git', 'show-ref', ref]).split()
- binascii.unhexlify(refs[0]) #Valid hex?
- self.sha1 = refs[0]
- self.ref = refs[1]
+ refs = ex.runv(['git', 'show-ref', ref]).split('\n')
+
+ # drop the refs that are not from origin
+ refs = [x.split() for x in refs if 'origin' in x]
+
+ binascii.unhexlify(refs[0][0]) #Valid hex?
+ self.sha1 = refs[0][0]
+ self.ref = refs[0][1]
except morphlib.execute.CommandFailure:
self._is_sha(ref)
def _is_sha(self, ref):
- if len(ref)!=40:
- raise InvalidTreeish(self.repo,ref)
+ if len(ref) != 40:
+ raise InvalidTreeish(self.original_repo, ref)
try:
binascii.unhexlify(ref)
@@ -73,13 +86,13 @@ class Treeish:
ex.runv(['git', 'rev-list', '--no-walk', ref])
self.sha1=ref
except (TypeError, morphlib.execute.CommandFailure):
- raise InvalidTreeish(self.repo,ref)
+ raise InvalidTreeish(self.original_repo, ref)
def export_sources(treeish, tar_filename):
'''Export the contents of a specific commit into a compressed tarball.'''
ex = morphlib.execute.Execute('.', msg=logging.debug)
- ex.runv(['git', 'archive', '-o', tar_filename, '--remote', treeish.repo,
- treeish.sha1])
+ ex.runv(['git', 'archive', '-o', tar_filename, '--remote',
+ 'file://%s' % treeish.repo, treeish.sha1])
def get_morph_text(treeish, filename):
'''Return a morphology from a git repository.'''
@@ -108,12 +121,6 @@ def add_remote(gitdir, name, url):
ex = morphlib.execute.Execute(gitdir, msg=logging.debug)
return ex.runv(['git', 'remote', 'add', '-f', name, url])
-# FIXME: All usage of this must die and Treeishes should be used
-def get_commit_id(repo, ref):
- '''Return the full SHA-1 commit id for a repo+ref.'''
- scheme, netlock, path, params, query, frag = urlparse.urlparse(repo)
- assert scheme == 'file'
- ex = morphlib.execute.Execute(path, msg=logging.debug)
- out = ex.runv(['git', 'rev-list', '-n1', ref])
- return out.strip()
-
+def update_remote(gitdir, name):
+ ex = morphlib.execute.Execute(gitdir, msg=logging.debug)
+ return ex.runv(['git', 'remote', 'update', name])
diff --git a/morphlib/morphology.py b/morphlib/morphology.py
index c530824c..9f9fa06b 100644
--- a/morphlib/morphology.py
+++ b/morphlib/morphology.py
@@ -23,30 +23,28 @@ class Morphology(object):
'''Represent a morphology: description of how to build binaries.'''
- def __init__(self, repo, ref, fp, baseurl=None):
- self.repo = repo
- self.ref = ref
+ def __init__(self, treeish, fp):
+ self.treeish = treeish
+ self.filename = fp.name
self._fp = fp
- self._baseurl = baseurl or ''
self._load()
def _load(self):
- logging.debug('Loading morphology %s' % self._fp.name)
+ logging.debug('Loading morphology %s from %s' %
+ (self._fp.name, self.treeish))
try:
self._dict = json.load(self._fp)
except ValueError:
- logging.error('Failed to load morphology %s' % self._fp.name)
+ logging.error('Failed to load morphology %s from %s' %
+ (self._fp.name, self.treeish))
raise
if self.kind == 'stratum':
for source in self.sources:
if 'repo' not in source:
source[u'repo'] = source['name']
- repo = self._join_with_baseurl(source['repo'])
- source[u'repo'] = unicode(repo)
-
- self.filename = self._fp.name
+ source[u'repo'] = unicode(source['repo'])
@property
def name(self):
@@ -110,20 +108,7 @@ class Morphology(object):
def test_stories(self):
return self._dict.get('test-stories', [])
- def _join_with_baseurl(self, url):
- is_relative = (':' not in url or
- '/' not in url or
- url.find('/') < url.find(':'))
- if is_relative:
- if not url.endswith('/'):
- url += '/'
- baseurl = self._baseurl
- if baseurl and not baseurl.endswith('/'):
- baseurl += '/'
- return baseurl + url
- else:
- return url
-
def __str__(self): # pragma: no cover
- return '%s|%s|%s' % (os.path.basename(os.path.dirname(self.repo)),
- self.ref, os.path.basename(self.filename))
+ return '%s|%s|%s' % (self.treeish.original_repo,
+ self.treeish.ref,
+ os.path.basename(self.filename))
diff --git a/morphlib/morphology_tests.py b/morphlib/morphology_tests.py
index 2df42315..db771fbb 100644
--- a/morphlib/morphology_tests.py
+++ b/morphlib/morphology_tests.py
@@ -14,13 +14,17 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import json
import StringIO
import unittest
import morphlib
+class FakeTreeish(object):
+
+ pass
+
+
class MockFile(StringIO.StringIO):
def __init__(self, *args, **kwargs):
@@ -30,19 +34,31 @@ class MockFile(StringIO.StringIO):
class MorphologyTests(unittest.TestCase):
+ def test_constructor_with_treeish(self):
+ faketreeish = FakeTreeish()
+ morph = morphlib.morphology.Morphology(
+ faketreeish,
+ MockFile('''
+ {
+ "name": "hello",
+ "kind": "chunk"
+ }'''))
+ self.assertEqual(morph.treeish, faketreeish)
+
def test_fails_invalid_chunk_morphology(self):
def failtest():
- morph = morphlib.morphology.Morphology(
- 'repo', 'ref',
- MockFile('''
- {
- "name": "hello",
- }'''))
+ morphlib.morphology.Morphology(
+ FakeTreeish(),
+ MockFile('''
+ {
+ "name": "hello",
+ }'''))
self.assertRaises(ValueError, failtest)
def test_accepts_valid_chunk_morphology(self):
+ faketreeish = FakeTreeish()
morph = morphlib.morphology.Morphology(
- 'repo', 'ref',
+ faketreeish,
MockFile('''
{
"name": "hello",
@@ -69,8 +85,7 @@ class MorphologyTests(unittest.TestCase):
}
}'''))
- self.assertEqual(morph.repo, 'repo')
- self.assertEqual(morph.ref, 'ref')
+ self.assertEqual(morph.treeish, faketreeish)
self.assertEqual(morph.filename, 'mockfile')
self.assertEqual(morph.name, 'hello')
self.assertEqual(morph.kind, 'chunk')
@@ -92,7 +107,7 @@ class MorphologyTests(unittest.TestCase):
def test_build_system_defaults_to_None(self):
morph = morphlib.morphology.Morphology(
- 'repo', 'ref',
+ FakeTreeish(),
MockFile('''
{
"name": "hello",
@@ -102,7 +117,7 @@ class MorphologyTests(unittest.TestCase):
def test_max_jobs_defaults_to_None(self):
morph = morphlib.morphology.Morphology(
- 'repo', 'ref',
+ FakeTreeish(),
MockFile('''
{
"name": "hello",
@@ -112,7 +127,7 @@ class MorphologyTests(unittest.TestCase):
def test_accepts_valid_stratum_morphology(self):
morph = morphlib.morphology.Morphology(
- 'repo', 'ref',
+ FakeTreeish(),
MockFile('''
{
"name": "hello",
@@ -124,22 +139,21 @@ class MorphologyTests(unittest.TestCase):
"ref": "ref"
}
]
- }'''),
- baseurl='git://example.com')
+ }'''))
self.assertEqual(morph.kind, 'stratum')
self.assertEqual(morph.filename, 'mockfile')
self.assertEqual(morph.sources,
[
{
u'name': u'foo',
- u'repo': u'git://example.com/foo/',
- u'ref': u'ref'
+ u'repo': u'foo',
+ u'ref': u'ref',
},
])
def test_accepts_valid_system_morphology(self):
morph = morphlib.morphology.Morphology(
- 'repo', 'ref',
+ FakeTreeish(),
MockFile('''
{
"name": "hello",
@@ -158,35 +172,3 @@ class MorphologyTests(unittest.TestCase):
self.assertEqual(morph.disk_size, '1G')
self.assertEqual(morph.strata, ['foo', 'bar'])
self.assertEqual(morph.test_stories, ['test-1', 'test-2'])
-
-
-class StratumRepoTests(unittest.TestCase):
-
- def stratum(self, repo):
- return morphlib.morphology.Morphology(
- 'repo', 'ref',
- MockFile('''
- {
- "name": "hello",
- "kind": "stratum",
- "sources":
- [
- {
- "name": "foo",
- "repo": "%s",
- "ref": "HEAD"
- }
- ]
- }''' % repo),
- baseurl='git://git.baserock.org/')
-
- def test_leaves_absolute_repo_in_source_dict_as_is(self):
- stratum = self.stratum('git://git.baserock.org/foo/')
- self.assertEqual(stratum.sources[0]['repo'],
- 'git://git.baserock.org/foo/')
-
- def test_makes_relative_repo_url_absolute_in_source_dict(self):
- stratum = self.stratum('foo')
- self.assertEqual(stratum.sources[0]['repo'],
- 'git://git.baserock.org/foo/')
-
diff --git a/morphlib/morphologyloader.py b/morphlib/morphologyloader.py
index 5a975a60..f59a8ddf 100644
--- a/morphlib/morphologyloader.py
+++ b/morphlib/morphologyloader.py
@@ -14,14 +14,12 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import os
import StringIO
import urlparse
import morphlib
-#FIXME should use Treeishes everywhere and get stuff from the SourceManager
class MorphologyLoader(object):
'''Load morphologies from git and parse them into Morphology objects.'''
@@ -30,31 +28,20 @@ class MorphologyLoader(object):
self.settings = settings
self.morphologies = {}
- def load(self, repo, ref, filename):
- base_url = self.settings['git-base-url']
- if not base_url.endswith('/'):
- base_url += '/'
- repo = urlparse.urljoin(base_url, repo)
-
- key = (repo, ref, filename)
-
+ def load(self, treeish, filename):
+ key = (treeish, filename)
if key in self.morphologies:
return self.morphologies[key]
else:
- morph = self._get_morph_from_git(repo, ref, filename)
+ morph = self._get_morph_from_git(treeish, filename)
self.morphologies[key] = morph
return morph
- def _get_morph_text(self, repo, ref, filename): # pragma: no cover
- path = urlparse.urlparse(repo).path
- t = morphlib.git.Treeish(path, ref)
- return morphlib.git.get_morph_text(t, filename)
-
- def _get_morph_from_git(self, repo, ref, filename):
- morph_text = self._get_morph_text(repo, ref, filename)
- scheme, netlock, path, params, query, frag = urlparse.urlparse(repo)
- f = StringIO.StringIO(morph_text)
- f.name = os.path.join(path, filename)
- morph = morphlib.morphology.Morphology(repo, ref, f,
- self.settings['git-base-url'])
- return morph
+ def _get_morph_text(self, treeish, filename): # pragma: no cover
+ return morphlib.git.get_morph_text(treeish, filename)
+
+ def _get_morph_from_git(self, treeish, filename):
+ morph_text = self._get_morph_text(treeish, filename)
+ fp = StringIO.StringIO(morph_text)
+ fp.name = filename
+ return morphlib.morphology.Morphology(treeish, fp)
diff --git a/morphlib/morphologyloader_tests.py b/morphlib/morphologyloader_tests.py
index 475ce391..60a6b77b 100644
--- a/morphlib/morphologyloader_tests.py
+++ b/morphlib/morphologyloader_tests.py
@@ -26,11 +26,16 @@ class MorphologyLoaderTests(unittest.TestCase):
loader = morphlib.morphologyloader.MorphologyLoader(settings)
loader._get_morph_text = self.get_morph_text
- morph1 = loader.load('repo', 'ref', 'hello.morph')
- morph2 = loader.load('repo', 'ref', 'hello.morph')
+ class FakeTreeish(object):
+ def __init__(self):
+ self.original_repo = 'test-repo'
+ faketreeish = FakeTreeish()
+
+ morph1 = loader.load(faketreeish, 'foo.morph')
+ morph2 = loader.load(faketreeish, 'foo.morph')
self.assertEqual(morph1, morph2)
- def get_morph_text(self, repo, ref, filename):
+ def get_morph_text(self, treeish, filename):
return ('''
{
"name": "foo",
diff --git a/morphlib/sourcemanager.py b/morphlib/sourcemanager.py
index f78b8cd6..ba97c3ef 100644
--- a/morphlib/sourcemanager.py
+++ b/morphlib/sourcemanager.py
@@ -46,16 +46,21 @@ class SourceNotFound(Exception):
class SourceManager(object):
- def __init__(self, cachedir, app):
- self.source_cache_dir = cachedir
+ def __init__(self, app, cachedir=None):
self.msg = app.msg
self.settings = app.settings
+ self.cached_treeishes = {}
+ self.cache_dir = cachedir
+ if not self.cache_dir:
+ self.cache_dir = os.path.join(app.settings['cachedir'], 'gits')
def _get_git_cache(self, repo):
name = quote_url(repo)
- location = self.source_cache_dir + '/' + name
+ location = self.cache_dir + '/' + name
if os.path.exists(location):
+ self.msg('updating cached version of %s' % location)
+ morphlib.git.update_remote(location, "origin")
return True, location
success = False
@@ -63,7 +68,7 @@ class SourceManager(object):
self.msg('Making sure we have a local cache of the git repo')
bundle = None
- if self.settings.has_key('bundle-server'):
+ if self.settings['bundle-server']:
bundle_server = self.settings['bundle-server']
if not bundle_server.endswith('/'):
bundle_server += '/'
@@ -77,7 +82,7 @@ class SourceManager(object):
try:
urllib2.urlopen(req)
self._wget(lookup_url)
- bundle = self.source_cache_dir + '/' + bundle
+ bundle = self.cache_dir + '/' + bundle
except urllib2.URLError:
self.msg("Unable to find bundle %s on %s" %
(bundle, bundle_server))
@@ -100,7 +105,7 @@ class SourceManager(object):
return success, location
def _wget(self,url): # pragma: no cover
- ex = morphlib.execute.Execute(self.source_cache_dir, msg=self.msg)
+ ex = morphlib.execute.Execute(self.cache_dir, msg=self.msg)
ex.runv(['wget', '-c', url])
def get_treeish(self, repo, ref):
@@ -132,13 +137,13 @@ class SourceManager(object):
full_repo = urlparse.urljoin(base_url, repo)
self.msg('cache git base_url=\'%s\' full repo url=\'%s\'' %
- (base_url,full_repo))
+ (base_url, full_repo))
success, gitcache = self._get_git_cache(full_repo);
if not success:
raise SourceNotFound(repo,ref)
- self.msg("creating treeish for %s ref %s" % (gitcache,ref))
- treeish = Treeish(gitcache, ref, self.msg)
+ self.msg("creating treeish for %s ref %s" % (gitcache, ref))
+ treeish = Treeish(gitcache, repo, ref, self.msg)
return treeish
diff --git a/morphlib/sourcemanager_tests.py b/morphlib/sourcemanager_tests.py
index c781327f..1fb0b6c0 100644
--- a/morphlib/sourcemanager_tests.py
+++ b/morphlib/sourcemanager_tests.py
@@ -25,8 +25,13 @@ import morphlib
class DummyApp(object):
- def __init__(self):
- self.settings = { 'git-base-url': ['.',] }
+
+ def __init__(self):
+ self.settings = {
+ 'git-base-url': ['.',],
+ 'bundle-server': None,
+ 'cachedir': '/foo/bar/baz',
+ }
self.msg = lambda msg: None
@@ -37,18 +42,29 @@ class SourceManagerTests(unittest.TestCase):
env = os.environ
env["DATADIR"]=self.temprepodir
subprocess.call("./tests/show-dependencies.setup", shell=True, env=env)
- self.temprepo = self.temprepodir + '/test-repo/'
+ self.temprepo = self.temprepodir + '/test-repo/'
bundle_name = morphlib.sourcemanager.quote_url(self.temprepo) + '.bndl'
subprocess.call("git bundle create %s/%s master" % (self.temprepodir, bundle_name),
- shell=True, cwd=self.temprepo)
+ shell=True, cwd=self.temprepo)
def tearDown(self):
shutil.rmtree(self.temprepodir)
+ def test_constructor_with_and_without_cachedir(self):
+ app = DummyApp()
+
+ tempdir = '/bla/bla/bla'
+ s = morphlib.sourcemanager.SourceManager(app, tempdir)
+ self.assertEqual(s.cache_dir, tempdir)
+
+ s = morphlib.sourcemanager.SourceManager(app)
+ self.assertEqual(s.cache_dir,
+ os.path.join(app.settings['cachedir'], 'gits'))
+
def test_get_sha1_treeish_for_self(self):
tempdir = tempfile.mkdtemp()
- s = morphlib.sourcemanager.SourceManager(tempdir, DummyApp())
+ s = morphlib.sourcemanager.SourceManager(DummyApp(), tempdir)
t = s.get_treeish(self.temprepo,
'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
self.assertEquals(t.sha1, 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
@@ -58,12 +74,12 @@ class SourceManagerTests(unittest.TestCase):
def test_get_sha1_treeish_for_self_twice(self):
tempdir = tempfile.mkdtemp()
- s = morphlib.sourcemanager.SourceManager(tempdir, DummyApp())
+ s = morphlib.sourcemanager.SourceManager(DummyApp(), tempdir)
t = s.get_treeish(self.temprepo,
'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
self.assertEquals(t.sha1, 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
- s = morphlib.sourcemanager.SourceManager(tempdir, DummyApp())
+ s = morphlib.sourcemanager.SourceManager(DummyApp(), tempdir)
t = s.get_treeish(self.temprepo,
'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
self.assertEquals(t.sha1, 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
@@ -73,9 +89,9 @@ class SourceManagerTests(unittest.TestCase):
def test_get_ref_treeish_for_self(self):
tempdir = tempfile.mkdtemp()
- s = morphlib.sourcemanager.SourceManager(tempdir, DummyApp())
+ s = morphlib.sourcemanager.SourceManager(DummyApp(), tempdir)
t = s.get_treeish(self.temprepo, 'master')
- self.assertEquals(t.ref, 'refs/heads/master')
+ self.assertEquals(t.ref, 'refs/remotes/origin/master')
shutil.rmtree(tempdir)
@@ -86,11 +102,11 @@ class SourceManagerTests(unittest.TestCase):
app = DummyApp()
app.settings['bundle-server'] = 'file://' + bundle_server_loc
- s = morphlib.sourcemanager.SourceManager(tempdir, app)
+ s = morphlib.sourcemanager.SourceManager(app, tempdir)
def wget(url):
path=urlparse(url).path
- shutil.copy(path, s.source_cache_dir)
+ shutil.copy(path, s.cache_dir)
s._wget = wget
@@ -100,17 +116,16 @@ class SourceManagerTests(unittest.TestCase):
shutil.rmtree(tempdir)
-
def test_get_sha1_treeish_for_self_bundle_fail(self):
tempdir = tempfile.mkdtemp()
app = DummyApp()
app.settings['bundle-server'] = 'file://' + self.temprepodir
- s = morphlib.sourcemanager.SourceManager(tempdir, app)
+ s = morphlib.sourcemanager.SourceManager(app, tempdir)
def wget(url):
path=urlparse(url).path
- shutil.copy(path, s.source_cache_dir)
+ shutil.copy(path, s.cache_dir)
s._wget = wget
self.assertRaises(morphlib.sourcemanager.SourceNotFound, s.get_treeish,
@@ -124,8 +139,7 @@ class SourceManagerTests(unittest.TestCase):
app = DummyApp()
app.settings['git-base-url'] = ['.', '/somewhere/else']
-
- s = morphlib.sourcemanager.SourceManager(tempdir, app)
+ s = morphlib.sourcemanager.SourceManager(app, tempdir)
t = s.get_treeish(self.temprepo,
'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
self.assertEquals(t.sha1, 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
diff --git a/tests/missing-ref.stderr b/tests/missing-ref.stderr
index a184134b..5a1ffb2a 100644
--- a/tests/missing-ref.stderr
+++ b/tests/missing-ref.stderr
@@ -1 +1 @@
-ERROR: non-existent-branch is an invalid reference for repo /chunk-repo/
+ERROR: non-existent-branch is an invalid reference for repo chunk-repo
diff --git a/tests/show-dependencies.stdout b/tests/show-dependencies.stdout
index c2db0c94..5ad63c65 100644
--- a/tests/show-dependencies.stdout
+++ b/tests/show-dependencies.stdout
@@ -1,133 +1,133 @@
dependency tree:
- test-repo|master|cairo.morph
- test-repo|master|dbus-glib.morph
- -> test-repo|master|dbus.morph
- -> test-repo|master|glib.morph
- test-repo|master|dbus.morph
- test-repo|master|exo.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4util.morph
- test-repo|master|fontconfig.morph
- test-repo|master|freetype.morph
- test-repo|master|garcon.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4util.morph
- test-repo|master|gdk-pixbuf.morph
- -> test-repo|master|glib.morph
- test-repo|master|glib.morph
- test-repo|master|gtk-stack.morph
- -> test-repo|master|cairo.morph
- -> test-repo|master|dbus-glib.morph
- -> test-repo|master|dbus.morph
- -> test-repo|master|fontconfig.morph
- -> test-repo|master|freetype.morph
- -> test-repo|master|gdk-pixbuf.morph
- -> test-repo|master|glib.morph
- -> test-repo|master|gtk.morph
- -> test-repo|master|pango.morph
- test-repo|master|gtk-xfce-engine.morph
- -> test-repo|master|garcon.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|xfconf.morph
- test-repo|master|gtk.morph
- -> test-repo|master|cairo.morph
- -> test-repo|master|gdk-pixbuf.morph
- -> test-repo|master|glib.morph
- -> test-repo|master|pango.morph
- test-repo|master|libxfce4ui.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|xfconf.morph
- test-repo|master|libxfce4util.morph
- -> test-repo|master|gtk-stack.morph
- test-repo|master|pango.morph
- -> test-repo|master|fontconfig.morph
- -> test-repo|master|freetype.morph
- test-repo|master|thunar.morph
- -> test-repo|master|exo.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- test-repo|master|tumbler.morph
- -> test-repo|master|gtk-stack.morph
- test-repo|master|xfce-core.morph
- -> test-repo|master|exo.morph
- -> test-repo|master|garcon.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|gtk-xfce-engine.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|libxfce4util.morph
- -> test-repo|master|thunar.morph
- -> test-repo|master|tumbler.morph
- -> test-repo|master|xfce4-appfinder.morph
- -> test-repo|master|xfce4-panel.morph
- -> test-repo|master|xfce4-session.morph
- -> test-repo|master|xfce4-settings.morph
- -> test-repo|master|xfconf.morph
- -> test-repo|master|xfdesktop.morph
- -> test-repo|master|xfwm4.morph
- test-repo|master|xfce4-appfinder.morph
- -> test-repo|master|garcon.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|xfconf.morph
- test-repo|master|xfce4-panel.morph
- -> test-repo|master|exo.morph
- -> test-repo|master|garcon.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- test-repo|master|xfce4-session.morph
- -> test-repo|master|exo.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|xfconf.morph
- test-repo|master|xfce4-settings.morph
- -> test-repo|master|exo.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|xfconf.morph
- test-repo|master|xfconf.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4util.morph
- test-repo|master|xfdesktop.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|xfconf.morph
- test-repo|master|xfwm4.morph
- -> test-repo|master|gtk-stack.morph
- -> test-repo|master|libxfce4ui.morph
- -> test-repo|master|xfconf.morph
+ test-repo|refs/remotes/origin/master|cairo.morph
+ test-repo|refs/remotes/origin/master|dbus-glib.morph
+ -> test-repo|refs/remotes/origin/master|dbus.morph
+ -> test-repo|refs/remotes/origin/master|glib.morph
+ test-repo|refs/remotes/origin/master|dbus.morph
+ test-repo|refs/remotes/origin/master|exo.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4util.morph
+ test-repo|refs/remotes/origin/master|fontconfig.morph
+ test-repo|refs/remotes/origin/master|freetype.morph
+ test-repo|refs/remotes/origin/master|garcon.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4util.morph
+ test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ -> test-repo|refs/remotes/origin/master|glib.morph
+ test-repo|refs/remotes/origin/master|glib.morph
+ test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|cairo.morph
+ -> test-repo|refs/remotes/origin/master|dbus-glib.morph
+ -> test-repo|refs/remotes/origin/master|dbus.morph
+ -> test-repo|refs/remotes/origin/master|fontconfig.morph
+ -> test-repo|refs/remotes/origin/master|freetype.morph
+ -> test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ -> test-repo|refs/remotes/origin/master|glib.morph
+ -> test-repo|refs/remotes/origin/master|gtk.morph
+ -> test-repo|refs/remotes/origin/master|pango.morph
+ test-repo|refs/remotes/origin/master|gtk-xfce-engine.morph
+ -> test-repo|refs/remotes/origin/master|garcon.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ test-repo|refs/remotes/origin/master|gtk.morph
+ -> test-repo|refs/remotes/origin/master|cairo.morph
+ -> test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ -> test-repo|refs/remotes/origin/master|glib.morph
+ -> test-repo|refs/remotes/origin/master|pango.morph
+ test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ test-repo|refs/remotes/origin/master|libxfce4util.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ test-repo|refs/remotes/origin/master|pango.morph
+ -> test-repo|refs/remotes/origin/master|fontconfig.morph
+ -> test-repo|refs/remotes/origin/master|freetype.morph
+ test-repo|refs/remotes/origin/master|thunar.morph
+ -> test-repo|refs/remotes/origin/master|exo.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ test-repo|refs/remotes/origin/master|tumbler.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ test-repo|refs/remotes/origin/master|xfce-core.morph
+ -> test-repo|refs/remotes/origin/master|exo.morph
+ -> test-repo|refs/remotes/origin/master|garcon.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|gtk-xfce-engine.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4util.morph
+ -> test-repo|refs/remotes/origin/master|thunar.morph
+ -> test-repo|refs/remotes/origin/master|tumbler.morph
+ -> test-repo|refs/remotes/origin/master|xfce4-appfinder.morph
+ -> test-repo|refs/remotes/origin/master|xfce4-panel.morph
+ -> test-repo|refs/remotes/origin/master|xfce4-session.morph
+ -> test-repo|refs/remotes/origin/master|xfce4-settings.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ -> test-repo|refs/remotes/origin/master|xfdesktop.morph
+ -> test-repo|refs/remotes/origin/master|xfwm4.morph
+ test-repo|refs/remotes/origin/master|xfce4-appfinder.morph
+ -> test-repo|refs/remotes/origin/master|garcon.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ test-repo|refs/remotes/origin/master|xfce4-panel.morph
+ -> test-repo|refs/remotes/origin/master|exo.morph
+ -> test-repo|refs/remotes/origin/master|garcon.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ test-repo|refs/remotes/origin/master|xfce4-session.morph
+ -> test-repo|refs/remotes/origin/master|exo.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ test-repo|refs/remotes/origin/master|xfce4-settings.morph
+ -> test-repo|refs/remotes/origin/master|exo.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ test-repo|refs/remotes/origin/master|xfconf.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4util.morph
+ test-repo|refs/remotes/origin/master|xfdesktop.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
+ test-repo|refs/remotes/origin/master|xfwm4.morph
+ -> test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> test-repo|refs/remotes/origin/master|xfconf.morph
build order:
group:
- test-repo|master|cairo.morph
- test-repo|master|dbus.morph
- test-repo|master|fontconfig.morph
- test-repo|master|freetype.morph
- test-repo|master|glib.morph
+ test-repo|refs/remotes/origin/master|cairo.morph
+ test-repo|refs/remotes/origin/master|dbus.morph
+ test-repo|refs/remotes/origin/master|fontconfig.morph
+ test-repo|refs/remotes/origin/master|freetype.morph
+ test-repo|refs/remotes/origin/master|glib.morph
group:
- test-repo|master|dbus-glib.morph
- test-repo|master|gdk-pixbuf.morph
- test-repo|master|pango.morph
+ test-repo|refs/remotes/origin/master|dbus-glib.morph
+ test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ test-repo|refs/remotes/origin/master|pango.morph
group:
- test-repo|master|gtk.morph
+ test-repo|refs/remotes/origin/master|gtk.morph
group:
- test-repo|master|gtk-stack.morph
+ test-repo|refs/remotes/origin/master|gtk-stack.morph
group:
- test-repo|master|libxfce4util.morph
- test-repo|master|tumbler.morph
+ test-repo|refs/remotes/origin/master|libxfce4util.morph
+ test-repo|refs/remotes/origin/master|tumbler.morph
group:
- test-repo|master|exo.morph
- test-repo|master|garcon.morph
- test-repo|master|xfconf.morph
+ test-repo|refs/remotes/origin/master|exo.morph
+ test-repo|refs/remotes/origin/master|garcon.morph
+ test-repo|refs/remotes/origin/master|xfconf.morph
group:
- test-repo|master|libxfce4ui.morph
+ test-repo|refs/remotes/origin/master|libxfce4ui.morph
group:
- test-repo|master|gtk-xfce-engine.morph
- test-repo|master|thunar.morph
- test-repo|master|xfce4-appfinder.morph
- test-repo|master|xfce4-panel.morph
- test-repo|master|xfce4-session.morph
- test-repo|master|xfce4-settings.morph
- test-repo|master|xfdesktop.morph
- test-repo|master|xfwm4.morph
+ test-repo|refs/remotes/origin/master|gtk-xfce-engine.morph
+ test-repo|refs/remotes/origin/master|thunar.morph
+ test-repo|refs/remotes/origin/master|xfce4-appfinder.morph
+ test-repo|refs/remotes/origin/master|xfce4-panel.morph
+ test-repo|refs/remotes/origin/master|xfce4-session.morph
+ test-repo|refs/remotes/origin/master|xfce4-settings.morph
+ test-repo|refs/remotes/origin/master|xfdesktop.morph
+ test-repo|refs/remotes/origin/master|xfwm4.morph
group:
- test-repo|master|xfce-core.morph
+ test-repo|refs/remotes/origin/master|xfce-core.morph