summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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