summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2012-03-26 15:15:25 +0100
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2012-03-26 15:15:25 +0100
commitca7900299dc32c7b59e896f5f4b9a38978f226a0 (patch)
treed1f15db7c51c83c1eb1fa11565adc2a295c48eec
parent7d7d597afd0a562916f106d259f4ff78383f9631 (diff)
parent9e18d7cc163ab95524349567fc8c4205cd0f11ce (diff)
downloadmorph-ca7900299dc32c7b59e896f5f4b9a38978f226a0.tar.gz
Add rudimentary branch+merge support
-rwxr-xr-xcheck1
-rw-r--r--doc/branching-merging-systems.mdwn340
-rwxr-xr-xmorph168
-rw-r--r--morphlib/sourcemanager.py6
-rwxr-xr-xscripts/cmd-filter40
-rwxr-xr-xscripts/list-tree25
-rwxr-xr-xscripts/run-git-in25
-rwxr-xr-xscripts/test-morph28
-rwxr-xr-xtests.branching/branch-creates-new-system-branch.script35
-rw-r--r--tests.branching/branch-creates-new-system-branch.stdout29
-rw-r--r--tests.branching/branch-when-branchdir-exists-locally.exit1
-rwxr-xr-xtests.branching/branch-when-branchdir-exists-locally.script27
-rw-r--r--tests.branching/branch-when-branchdir-exists-locally.stderr1
-rwxr-xr-xtests.branching/checkout-existing-branch.script33
-rw-r--r--tests.branching/checkout-existing-branch.stdout12
-rwxr-xr-xtests.branching/edit-checkouts-existing-chunk.script39
-rw-r--r--tests.branching/edit-checkouts-existing-chunk.stdout7
-rwxr-xr-xtests.branching/edit-clones-chunk.script44
-rw-r--r--tests.branching/edit-clones-chunk.stdout34
-rwxr-xr-xtests.branching/init-cwd.script25
-rw-r--r--tests.branching/init-cwd.stdout2
-rwxr-xr-xtests.branching/init-default.script24
-rw-r--r--tests.branching/init-default.stdout2
-rwxr-xr-xtests.branching/init-existing.script24
-rw-r--r--tests.branching/init-existing.stdout2
-rw-r--r--tests.branching/init-newdir.exit1
-rwxr-xr-xtests.branching/init-newdir.script24
-rw-r--r--tests.branching/init-newdir.stderr1
-rw-r--r--tests.branching/init-nonempty.exit1
-rwxr-xr-xtests.branching/init-nonempty.script24
-rw-r--r--tests.branching/init-nonempty.stderr1
-rwxr-xr-xtests.branching/merge-explicitly-named-repos.script48
-rw-r--r--tests.branching/merge-explicitly-named-repos.stdout1
-rw-r--r--tests.branching/minedir-not-found.exit1
-rwxr-xr-xtests.branching/minedir-not-found.script19
-rw-r--r--tests.branching/minedir-not-found.stderr1
-rwxr-xr-xtests.branching/minedir.script20
-rw-r--r--tests.branching/minedir.stdout1
-rwxr-xr-xtests.branching/setup99
-rwxr-xr-xtests.branching/show-system-branch-shows-name-correctly.script31
-rw-r--r--tests.branching/show-system-branch-shows-name-correctly.stdout1
-rwxr-xr-xtests.branching/teardown22
-rwxr-xr-xtests.branching/workflow.script39
-rw-r--r--tests/missing-ref.stderr2
-rwxr-xr-xtests/show-dependencies.script4
-rw-r--r--tests/show-dependencies.stdout244
46 files changed, 1432 insertions, 127 deletions
diff --git a/check b/check
index 0b37fb58..8af05668 100755
--- a/check
+++ b/check
@@ -21,6 +21,7 @@ set -e
python setup.py clean check
cmdtest tests
+cmdtest tests.branching
if [ $(whoami) = root ] && command -v mkfs.btrfs > /dev/null
then
cmdtest tests.as-root
diff --git a/doc/branching-merging-systems.mdwn b/doc/branching-merging-systems.mdwn
new file mode 100644
index 00000000..3e781b5a
--- /dev/null
+++ b/doc/branching-merging-systems.mdwn
@@ -0,0 +1,340 @@
+Branching and merging at the system level in Baserock
+=====================================================
+
+As I write this, Baserock consists of just under 70 upstream projects,
+each of which we keep in their own git repository. We need a way to
+manage changes to them in a sensible manner, particularly when things
+touch more than one repository. What we need is a way to do branch
+and merge the whole system, across all our git repositories, with
+similar ease and efficiency as what git provides for an individual
+project. Where possible we need to allow the use of raw git so that
+we do not constrain our developers unnecessarily.
+
+There are other things we will want to do across all the Baserock git
+repositories, but that is outside the scope of this document, and will
+be dealt with later.
+
+A couple of use cases:
+
+* I have a problem on a particular device, and want to make changes to
+ analyze and fix it. I need to branch the specific version of everything
+ that was using the system image version that the device was running.
+ I then want to be able to make changes to selected components and build
+ the system with those changes. Everything I do not explicitly touch should
+ stay at the same version.
+* I am developing Baserock and I want to fix something, or add a new
+ feature, or other such change. I want to take the current newest
+ version of everything (the mainline development branch, whatever it
+ might be named), and make changes to some components, and build and
+ test those changes. While I'm doing that, I don't want to get random
+ other changes by other people, except when I explicitly ask for them
+ (e.g. "git pull" on an individual repository.), to avoid unnecessary
+ conflicts and building changes that don't affect my changes.
+
+In both users cases, when I'm done, I want to get my changes into the
+relevant branches. This might happen by merging my changes directly,
+by generating a pull request on a git server, or by generating a patch
+series for each affected repository to be mailed to people who can do
+the merging.
+
+Overview
+--------
+
+We want a clear, convenient, and efficient way of working with multiple
+repositories and multiple projects at the same time. To manage this,
+we introduce the following concepts (FIXME: naming needs attention):
+
+* **git repository** is exactly the same as usually with git, as are
+ all other concepts related to git
+* **system branch** is a collection of branches in individual git
+ repositories that together form a particular line of development of
+ the whole system; in other words, given all the git repositories
+ that are part of Baserock, system branch `foo` consists of branch
+ `foo` in each git repository that has a branch with that name
+* **system branch directory** contains git repositories relevant to
+ a system branch; it need not contain all the repositories, just the
+ ones that are being worked on by the user, or that the user for
+ other reasons have checked out
+* **morph mine** is where all Morph keeps global
+ state and shared caches, so that creating a system branch directory
+ is a fairly cheap operation; all the system branch directories are
+ inside the morph mine directory
+
+As a picture:
+
+ /home/liw/ -- user's home directory
+ baserock/ -- morph mine
+ .morph/ -- morph shared state, cache, etc
+ unstable/ -- system branch directory: mainline devel
+ morphs/ -- git repository for system, stratum morphs
+ magnetic-frobbles/ -- system branch directory: new feature
+ morphs/ -- system branch specific changes to morphs
+ linux/ -- ditto for linux
+
+To use the system branching and merging, you do the following (which we'll
+cover in more detail later):
+
+1. Initialize the morph mine. This creates the `.morph` directory and
+ populates it with some initial stuff. You can have as many mines as
+ you want, but you don't have to have more than one, and you only
+ need to initialize it once.
+2. Branch the system from some version of it (e.g., `master`) to work
+ on a new feature or bug fix.
+ This creates a system branch directory under the mine directory.
+ The system branch directory initially contains a clone of the `morphs`
+ git repository, with some changes specific to this system branch.
+ (See petrification, below.)
+3. Edit one or more components (chunks) in the project. This typically
+ requires adding more clones of git repositories inside the system
+ branch directory.
+4. Build, test, and fix, repeating as necessary. This requires using
+ git directly (not via morph) in the git repositories inside the
+ system branch directory.
+5. Merge the changes to relevant target branches. Depending on what the
+ change was, it may be necessary ot merge into many branches, e.g.,
+ for each stable release branch.
+
+Walkthrough
+-----------
+
+Let's walk through what happens, making things more concrete. This is
+still fairly high level, and implementation details will come later.
+
+ morph init ~/baserock
+
+This creates the `~/baserock` directory if it does not exist, and then
+initializes it as a "morph mine" directory, by creating a `.morph`
+subdirectory. `.morph` will contain the Morph cache directory, and
+other shared state between the various branches. As part of the cache,
+any git repositories that Morph clones get put into the cache first,
+and cloned into the system branch directories from there (probably
+using hard-linking for speed), so that if there's a need for another
+clone of the repository, it does not need to be cloned from a server
+a second time.
+
+ cd ~/baserock
+ morph branch liw/foo
+ morph branch liw/foo baserock/stable-1.0
+ morph branch liw/foo --branch-off-system=/home/liw/system.img
+
+Create a new system branch, and the corresponding system branch
+directory. The three versions shown differ in the starting point
+of the branch: the first one uses the `master` branch in `morphs`,
+the second one uses the named branch instead, and the third one
+gets the SHA-1s from a system image file.
+
+Also, clone the `morphs` git repository inside the system branch
+directory.
+
+ cd ~/baserock/liw/foo/morphs
+ morph petrify base-system.morph devel-system.morph
+ git commit -a
+
+Modify the specified morphologies (or the stratum morphologies they
+refer to) to nail down the references to chunk repositories to use SHA-1s
+instead of branch names or whatever. The changes need to be committed
+to git manually, so that the user has a chance to give a good commit
+message.
+
+Petrification can be done by resolving the chunk references against
+the current state of the git repositories, or it can be done by getting
+the SHA-1s directly from a system image, or a data file.
+
+ cd ~/baserock/liw/foo
+ morph edit linux
+
+Tell Morph that you want to edit a particular component (chunk).
+This will clone the repository into the system branch directory,
+at the point in history indicated by the morphologies in the
+local version of `morphs`.
+
+ cd ~/baserock/liw/foo
+ morph git -- log -p master..HEAD
+
+This allows running a git command in each git repository in a
+system branch directory. Morph may offer short forms ("morph status")
+as well, for things that are needed very commonly.
+
+ cd ~/baserock/baserock/mainline
+ morph merge liw/foo
+
+This merges the changes made in the `liw/foo` branch into the
+`baserock/mainline` branch. The petrification changes are automatically
+undone, since they're not going to be wanted in the merge.
+
+ cd ~/baserock
+ morph mass-merge liw/foo baserock/stable*
+
+Do the merge from `liw/foo` to every branch matching `baserock/stable*`
+(as expanded by the shell). This is a wrapper around the simpler
+`morph merge` command to make it easier to push a change into many
+branches (e.g., a security fix to all stable branches).
+
+
+Implementation: `morph init`
+--------------
+
+Usage:
+
+ morph init [DIR]
+
+DIR defaults to the current working directory. If DIR is given,
+but does not exist, it is created.
+
+* Create `DIR/.morph` and the subdirectory `cache` inside it.
+
+
+Implementation: `morph branch`
+--------------
+
+Usage:
+
+ morph branch BRANCH [COMMIT]
+
+This needs to be run in the morph mine directory (the one initialized
+with `morph init`).
+
+* If `./BRANCH` as a directory exists, abort.
+* Create `./BRANCH` directory.
+* Clone the `morphs` repository to `BRANCH/morphs`.
+* Create a new branch called `BRANCH` in morphs, based either the tip of
+ `master` or from `COMMIT` if given. Store the SHA-1 of the branch origin
+ in some way so we get at it later.
+
+
+Implementation: `morph checkout`
+--------------
+
+Usage:
+
+ morph checkout BRANCH
+
+This needs to be run in the morph mine directory. It works like
+`morph branch`, except it does not create the new branch and requires
+it to exist instead.
+
+* If `./BRANCH` as a directory exists, abort.
+* Create `./BRANCH` directory.
+* Clone the `morphs` repository to `BRANCH/morphs`.
+* Run `git checkout BRANCH` in the `morphs` repository.
+
+
+Implementation: `morph petrify`
+--------------
+
+Usage:
+
+ morph petrify [MORPH]...
+ morph petrify --petrify-from-system FILE
+
+This needs to be run in the `morphs` git repository in a system branch.
+
+In the first form:
+
+* read each of the given morphologies; if the morphology is a system one,
+ follow references to stratum morphologies and process those instead
+* in each stratum morphology, replace a reference to a chunk with the
+ absolute SHA-1: if the original reference was, say, `baserock/morph`,
+ get the SHA-1 of the current tip commit in that branch and replace
+ the reference in the morphology
+
+In the second form:
+
+* extract the system and stratum morphologies used in the system image file;
+ these are in a petrified form already
+* copy the morphologies to the current working directory, overwriting the
+ files from git
+
+In either case, the results need to be committed (with normal git commands)
+by the user.
+
+
+Implementation: `morph edit`
+--------------
+
+Usage:
+
+ morph edit REPO MORPH...
+
+where `REPO` is a chunk repository (absolute URL or one relative to one of
+the `git-base-url` values). The command must be run in the `morphs`
+directory of the system branch.
+
+* `git clone REPOURL` where the URL is constructed with `git-base-url`
+ if necessary.
+* `git branch BRANCH REF` where `BRANCH` is the branch name given to
+ `morph branch` and `REF` is the reference to the chunk we want to edit,
+ as specified in morphologies.
+* Modify the affected morphologies to refer to the repository using
+ the `BRANCH` name, and commit those changes.
+
+If the specified morphology is not a stratum morphology (that is, it is
+a system one), we check all the stratum morphologies mentioned and find
+the one that refers to the specified repository.
+
+Multiple morphologies can be specified. They must have the same original
+reference to the repository. However, they will all be modified.
+
+
+Implementation: `morph git`
+--------------
+
+Usage:
+
+ morph git -- log -p master..HEAD
+
+This is to be run in the morph mine. It runs git with the arguments on
+the command line in each local git repository in the mine. (The `--` is
+only necessary if the git arguments are to contain options.)
+
+
+Implementation: `morph merge`
+--------------
+
+Usage:
+
+ morph merge BRANCH
+
+This needs to be run inside a system branch directory's `morphs`
+repository, and `BRANCH` must be another system branch checked out
+in the morph mine.
+
+* In each git repository modified by the `BRANCH` system branch,
+ run `git merge --no-commit BRANCH`, then undo any changes to
+ stratum morphologies made by `morph edit`, and finally commit
+ the changes.
+
+
+Implementation: `morph mass-merge`
+--------------
+
+Usage:
+
+ morph mass-merge BRANCH [TARGET]...
+
+To be run in the morph mine directory.
+
+This just runs `morph merge BRANCH` in each `TARGET` system branch.
+
+
+Implementation: `morph cherry-pick`
+--------------
+
+Usage:
+
+ morph cherry-pick BRANCH [COMMIT]...
+ morph cherry-pick BRANCH --since-branch-point
+
+To be run in the system branch directory.
+
+In the first form:
+
+* For each git repository modified by the `BRANCH` system branch,
+ run `git cherry-pick COMMIT` for each `COMMIT`.
+
+In the second form:
+
+* For each git repository modified by the `BRANCH` system branch,
+ run `git cherry-pick` giving it a list of all commits made after
+ the system branch was created.
+
diff --git a/morph b/morph
index 92fe5d76..754f3b8e 100755
--- a/morph
+++ b/morph
@@ -403,6 +403,172 @@ class Morph(cliapp.Application):
morphlib.fsutils.undo_device_mapping(ex, paths[name])
factory.remove_staging()
+ def cmd_init(self, args):
+ '''Initialize a mine.'''
+
+ if not args:
+ args = ['.']
+ elif len(args) > 1:
+ raise cliapp.AppException('init must get at most one argument')
+
+ dirname = args[0]
+
+ if os.path.exists(dirname):
+ if os.listdir(dirname) != []:
+ raise cliapp.AppException('can only initialize empty '
+ 'directory: %s' % dirname)
+ else:
+ raise cliapp.AppException('can only initialize an existing '
+ 'empty directory: %s' % dirname)
+
+ os.mkdir(os.path.join(dirname, '.morph'))
+
+ def _deduce_mine_directory(self):
+ dirname = os.getcwd()
+ while dirname != '/':
+ dot_morph = os.path.join(dirname, '.morph')
+ if os.path.isdir(dot_morph):
+ return dirname
+ dirname = os.path.dirname(dirname)
+ return None
+
+ def cmd_minedir(self, args):
+ '''Find morph mine directory from current working directory.'''
+
+ dirname = self._deduce_mine_directory()
+ if dirname is None:
+ raise cliapp.AppException("Can't find the mine directory")
+ self.output.write('%s\n' % dirname)
+
+ def _clone_to_directory(self, dirname, repo, ref):
+ '''Clone a repository below a directory.
+
+ As a side effect, clone it into the morph repository.
+
+ '''
+
+ # Get the repository into the cache.
+ tempdir = morphlib.tempdir.Tempdir(self.settings['tempdir'])
+ morph_loader = MorphologyLoader(self.settings)
+ source_manager = morphlib.sourcemanager.SourceManager(self,
+ update=not self.settings['no-git-update'])
+ treeish = source_manager.get_treeish(repo, ref)
+
+ # Clone it from cache to target directory.
+ morphlib.git.clone(dirname, treeish.repo, self.msg)
+
+ # Set the origin to point at the original repository.
+ morphlib.git.set_remote(dirname, 'origin', treeish.original_repo)
+
+ # Update remotes.
+ self.runcmd(['git', 'remote', 'update'], cwd=dirname)
+
+ def cmd_branch(self, args):
+ '''Branch the whole system.'''
+
+ if len(args) != 1:
+ raise cliapp.AppException('morph branch needs name of branch '
+ 'as parameter')
+
+ new_branch = args[0]
+ repo = 'morphs'
+ commit = 'master'
+
+ # Create the system branch directory.
+ os.makedirs(new_branch)
+
+ # Clone into system branch directory.
+ new_repo = os.path.join(new_branch, os.path.basename(repo))
+ self._clone_to_directory(new_repo, repo, commit)
+
+ # Create a new branch in the local morphs repository.
+ self.runcmd(['git', 'checkout', '-b', new_branch, commit],
+ cwd=new_repo)
+
+ def cmd_checkout(self, args):
+ '''Check out an existing system branch.'''
+
+ if len(args) != 1:
+ raise cliapp.AppException('morph checkout needs name of '
+ 'branch as parameter')
+
+ system_branch = args[0]
+ repo = 'morphs'
+
+ # Create the system branch directory.
+ os.makedirs(system_branch)
+
+ # Clone into system branch directory.
+ new_repo = os.path.join(system_branch, os.path.basename(repo))
+ self._clone_to_directory(new_repo, repo, system_branch)
+
+ def _deduce_system_branch(self):
+ minedir = self._deduce_mine_directory()
+ if minedir is None:
+ return None
+
+ if not minedir.endswith('/'):
+ minedir += '/'
+
+ cwd = os.getcwd()
+ if not cwd.startswith(minedir):
+ return None
+
+ return os.path.dirname(cwd[len(minedir):])
+
+ def cmd_show_system_branch(self, args):
+ '''Print name of current system branch.
+
+ This must be run in the system branch's ``morphs`` repository.
+
+ '''
+
+ system_branch = self._deduce_system_branch()
+ if system_branch is None:
+ raise cliapp.AppException("Can't determine system branch")
+ self.output.write('%s\n' % system_branch)
+
+ def cmd_edit(self, args):
+ '''Edit a component in a system branch.'''
+
+ if len(args) != 2:
+ raise cliapp.AppException('morph edit must get a repository name '
+ 'and commit ref as argument')
+
+ repo = args[0]
+ ref = args[1]
+
+ mine_directory = self._deduce_mine_directory()
+ system_branch = self._deduce_system_branch()
+ new_repo = os.path.join(mine_directory, system_branch,
+ os.path.basename(repo))
+ self._clone_to_directory(new_repo, repo, ref)
+
+ system_branch = self._deduce_system_branch()
+ if system_branch == ref:
+ self.runcmd(['git', 'checkout', system_branch],
+ cwd=new_repo)
+ else:
+ self.runcmd(['git', 'checkout', '-b', system_branch, ref],
+ cwd=new_repo)
+
+ def cmd_merge(self, args):
+ '''Merge specified repositories from another system branch.'''
+
+ if len(args) == 0:
+ raise cliapp.AppException('morph merge must get a branch name '
+ 'and some repo names as arguments')
+
+ other_branch = args[0]
+ mine = self._deduce_mine_directory()
+ this_branch = self._deduce_system_branch()
+
+ for repo in args[1:]:
+ basename = os.path.basename(repo)
+ pull_from = os.path.join(mine, other_branch, basename)
+ repo_dir = os.path.join(mine, this_branch, basename)
+ self.runcmd(['git', 'pull', pull_from, other_branch], cwd=repo_dir)
+
def msg(self, msg):
'''Show a message to the user about what is going on.'''
logging.debug(msg)
@@ -429,7 +595,7 @@ class Morph(cliapp.Application):
msg('# %s' % ' | '.join(commands))
# run the command line
- cliapp.Application.runcmd(self, argv, *args, **kwargs)
+ return cliapp.Application.runcmd(self, argv, *args, **kwargs)
# This is in morph so that policy is easily visible, and not embedded
# deep down in the call stack.
diff --git a/morphlib/sourcemanager.py b/morphlib/sourcemanager.py
index 21c99fa9..2747a2c9 100644
--- a/morphlib/sourcemanager.py
+++ b/morphlib/sourcemanager.py
@@ -164,6 +164,7 @@ class SourceManager(object):
repo_urls = [urlparse.urljoin(fixup_url(x), repo)
for x in self.settings['git-base-url']]
+ orig_url = None
cached_repo = None
errors = []
@@ -172,6 +173,7 @@ class SourceManager(object):
quoted_url = quote_url(repo_url)
cached_repo_dirname = os.path.join(self.cache_dir, quoted_url)
if os.path.exists(cached_repo_dirname):
+ orig_url = repo_url
cached_repo = cached_repo_dirname
break
@@ -183,6 +185,7 @@ class SourceManager(object):
cached_repo, error = self._cache_repo_from_bundle(server,
repo_url)
if cached_repo:
+ orig_url = repo_url
break
else:
errors.append(error)
@@ -193,6 +196,7 @@ class SourceManager(object):
for repo_url in repo_urls:
cached_repo, error = self._cache_repo_from_url(repo_url)
if cached_repo:
+ orig_url = repo_url
break
else:
errors.append(error)
@@ -227,7 +231,7 @@ class SourceManager(object):
# we should have a cached version of the repo now, return a treeish
# for the repo and ref tuple
- treeish = morphlib.git.Treeish(cached_repo, repo, ref, self.msg)
+ treeish = morphlib.git.Treeish(cached_repo, orig_url, ref, self.msg)
self.indent_less()
return treeish
diff --git a/scripts/cmd-filter b/scripts/cmd-filter
new file mode 100755
index 00000000..d2f4f784
--- /dev/null
+++ b/scripts/cmd-filter
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Wrapper around morph for use by cmdtest tests. This does several things:
+#
+# * simpler command lines for running morph, so that each test does not
+# need to add --no-default-config and other options every time
+# * replace temporary filenames ($DATADIR) in the output with a known
+# string ("TMP"), so that test output is deterministic
+
+set -eu
+
+if "$@" > "$DATADIR/stdout" 2> "$DATADIR/stderr"
+then
+ exit=0
+else
+ exit=1
+fi
+
+sed -i "s,$DATADIR,TMP,g" "$DATADIR/stdout" "$DATADIR/stderr"
+cat "$DATADIR/stdout"
+cat "$DATADIR/stderr" 1>&2
+
+rm -f "$DATADIR/stdout" "$DATADIR/stderr"
+
+exit "$exit"
+
diff --git a/scripts/list-tree b/scripts/list-tree
new file mode 100755
index 00000000..2b3a6aa9
--- /dev/null
+++ b/scripts/list-tree
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# List contents of a directory tree in a reproducible manner: only include
+# details that we care about, and that won't be changing between test runs
+# or test environments.
+
+set -eu
+
+export LC_ALL=C
+cd "$1"
+find -printf '%y %p\n' | sort
diff --git a/scripts/run-git-in b/scripts/run-git-in
new file mode 100755
index 00000000..80b87d1a
--- /dev/null
+++ b/scripts/run-git-in
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Run git in a given directory.
+
+
+set -eu
+
+cd "$1"
+shift
+"$SRCDIR/scripts/cmd-filter" git "$@"
diff --git a/scripts/test-morph b/scripts/test-morph
new file mode 100755
index 00000000..3c9e1131
--- /dev/null
+++ b/scripts/test-morph
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Wrapper around morph for use by cmdtest tests. This does several things:
+#
+# * simpler command lines for running morph, so that each test does not
+# need to add --no-default-config and other options every time
+# * replace temporary filenames ($DATADIR) in the output with a known
+# string ("TMP"), so that test output is deterministic
+
+set -eu
+
+"$SRCDIR/scripts/cmd-filter" "$SRCDIR/morph" --no-default-config \
+ --config="$DATADIR/morph.conf" "$@"
+
diff --git a/tests.branching/branch-creates-new-system-branch.script b/tests.branching/branch-creates-new-system-branch.script
new file mode 100755
index 00000000..61e9daf4
--- /dev/null
+++ b/tests.branching/branch-creates-new-system-branch.script
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Make sure "morph branch" creates a new system branch.
+
+set -eu
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+
+"$SRCDIR/scripts/test-morph" branch newbranch
+
+echo "File tree:"
+"$SRCDIR/scripts/list-tree" . | grep -v '/\.git/' |
+ sed 's,/cache/gits/file_[^/]*_,/cache/gits/file_,'
+
+echo "Current branches:"
+"$SRCDIR/scripts/run-git-in" newbranch/morphs branch
+
+echo "Current origin:"
+"$SRCDIR/scripts/run-git-in" newbranch/morphs remote show origin |
+ sed 's,\(TMP/mine/\.morph/cache/gits/file_\).*_,\1,g'
diff --git a/tests.branching/branch-creates-new-system-branch.stdout b/tests.branching/branch-creates-new-system-branch.stdout
new file mode 100644
index 00000000..6a3a9b98
--- /dev/null
+++ b/tests.branching/branch-creates-new-system-branch.stdout
@@ -0,0 +1,29 @@
+File tree:
+d .
+d ./.morph
+d ./.morph/cache
+d ./.morph/cache/gits
+d ./.morph/cache/gits/file_morphs
+d ./.morph/cache/gits/file_morphs/.git
+d ./newbranch
+d ./newbranch/morphs
+d ./newbranch/morphs/.git
+f ./newbranch/morphs/hello-stratum.chunk
+f ./newbranch/morphs/hello-system.chunk
+Current branches:
+ master
+* newbranch
+Current origin:
+* remote origin
+ Fetch URL: file://TMP/morphs
+ Push URL: file://TMP/morphs
+ HEAD branch (remote HEAD is ambiguous, may be one of the following):
+ alfred
+ master
+ Remote branches:
+ alfred tracked
+ master tracked
+ Local branch configured for 'git pull':
+ master merges with remote master
+ Local ref configured for 'git push':
+ master pushes to master (up to date)
diff --git a/tests.branching/branch-when-branchdir-exists-locally.exit b/tests.branching/branch-when-branchdir-exists-locally.exit
new file mode 100644
index 00000000..d00491fd
--- /dev/null
+++ b/tests.branching/branch-when-branchdir-exists-locally.exit
@@ -0,0 +1 @@
+1
diff --git a/tests.branching/branch-when-branchdir-exists-locally.script b/tests.branching/branch-when-branchdir-exists-locally.script
new file mode 100755
index 00000000..4692b5f6
--- /dev/null
+++ b/tests.branching/branch-when-branchdir-exists-locally.script
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Make sure "morph branch" fails when the system branch directory already
+# exists.
+
+set -eu
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+
+mkdir newbranch
+"$SRCDIR/scripts/test-morph" branch newbranch
+
diff --git a/tests.branching/branch-when-branchdir-exists-locally.stderr b/tests.branching/branch-when-branchdir-exists-locally.stderr
new file mode 100644
index 00000000..6c56c250
--- /dev/null
+++ b/tests.branching/branch-when-branchdir-exists-locally.stderr
@@ -0,0 +1 @@
+ERROR: newbranch: File exists
diff --git a/tests.branching/checkout-existing-branch.script b/tests.branching/checkout-existing-branch.script
new file mode 100755
index 00000000..74941e18
--- /dev/null
+++ b/tests.branching/checkout-existing-branch.script
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Verify that "morph checkout master" works.
+
+
+set -eu
+
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" checkout master
+
+echo "File tree:"
+"$SRCDIR/scripts/list-tree" . | grep -v '/\.git/' |
+ sed 's,/cache/gits/file_[^/]*_,/cache/gits/file_,'
+
+echo "Current branches:"
+"$SRCDIR/scripts/run-git-in" master/morphs branch
diff --git a/tests.branching/checkout-existing-branch.stdout b/tests.branching/checkout-existing-branch.stdout
new file mode 100644
index 00000000..4b29db80
--- /dev/null
+++ b/tests.branching/checkout-existing-branch.stdout
@@ -0,0 +1,12 @@
+File tree:
+d .
+d ./.morph
+d ./.morph/cache
+d ./.morph/cache/gits
+d ./.morph/cache/gits/file_morphs
+d ./.morph/cache/gits/file_morphs/.git
+d ./master
+d ./master/morphs
+d ./master/morphs/.git
+Current branches:
+* master
diff --git a/tests.branching/edit-checkouts-existing-chunk.script b/tests.branching/edit-checkouts-existing-chunk.script
new file mode 100755
index 00000000..583f19e1
--- /dev/null
+++ b/tests.branching/edit-checkouts-existing-chunk.script
@@ -0,0 +1,39 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Verify that "morph edit" clones a chunk repository into a system branch.
+
+
+set -eu
+
+# Checkout the master system branch.
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" checkout alfred
+
+# Edit the hello chunk in alfred.
+cd alfred/morphs
+"$SRCDIR/scripts/test-morph" edit hello alfred
+
+echo "Current branches, morphs:"
+"$SRCDIR/scripts/run-git-in" "$DATADIR/mine/alfred/morphs" branch
+
+echo "Current branches, hello:"
+"$SRCDIR/scripts/run-git-in" "$DATADIR/mine/alfred/hello" branch
+
+echo "Files in hello:"
+ls "$DATADIR/mine/alfred/hello"
diff --git a/tests.branching/edit-checkouts-existing-chunk.stdout b/tests.branching/edit-checkouts-existing-chunk.stdout
new file mode 100644
index 00000000..a06832c2
--- /dev/null
+++ b/tests.branching/edit-checkouts-existing-chunk.stdout
@@ -0,0 +1,7 @@
+Current branches, morphs:
+* master
+Current branches, hello:
+* alfred
+ master
+Files in hello:
+hello.chunk
diff --git a/tests.branching/edit-clones-chunk.script b/tests.branching/edit-clones-chunk.script
new file mode 100755
index 00000000..f6f4e5e8
--- /dev/null
+++ b/tests.branching/edit-clones-chunk.script
@@ -0,0 +1,44 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Verify that "morph edit" clones a chunk repository into a system branch.
+
+
+set -eu
+
+# Create system branch.
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" branch newbranch
+
+# Edit chunk.
+cd newbranch/morphs
+"$SRCDIR/scripts/test-morph" edit hello master
+
+echo "Current branches, morphs:"
+"$SRCDIR/scripts/run-git-in" "$DATADIR/mine/newbranch/morphs" branch
+
+echo "Current origin, morphs:"
+"$SRCDIR/scripts/run-git-in" "$DATADIR/mine/newbranch/morphs" \
+ remote show origin | sed 's,\(TMP/mine/\.morph/cache/gits/file_\).*_,\1,g'
+
+echo "Current branches, hello:"
+"$SRCDIR/scripts/run-git-in" "$DATADIR/mine/newbranch/hello" branch
+
+echo "Current origin, hello:"
+"$SRCDIR/scripts/run-git-in" "$DATADIR/mine/newbranch/hello" \
+ remote show origin | sed 's,\(TMP/mine/\.morph/cache/gits/file_\).*_,\1,g'
diff --git a/tests.branching/edit-clones-chunk.stdout b/tests.branching/edit-clones-chunk.stdout
new file mode 100644
index 00000000..4bbf909e
--- /dev/null
+++ b/tests.branching/edit-clones-chunk.stdout
@@ -0,0 +1,34 @@
+Current branches, morphs:
+ master
+* newbranch
+Current origin, morphs:
+* remote origin
+ Fetch URL: file://TMP/morphs
+ Push URL: file://TMP/morphs
+ HEAD branch (remote HEAD is ambiguous, may be one of the following):
+ alfred
+ master
+ Remote branches:
+ alfred tracked
+ master tracked
+ Local branch configured for 'git pull':
+ master merges with remote master
+ Local ref configured for 'git push':
+ master pushes to master (up to date)
+Current branches, hello:
+ master
+* newbranch
+Current origin, hello:
+* remote origin
+ Fetch URL: file://TMP/hello
+ Push URL: file://TMP/hello
+ HEAD branch (remote HEAD is ambiguous, may be one of the following):
+ alfred
+ master
+ Remote branches:
+ alfred tracked
+ master tracked
+ Local branch configured for 'git pull':
+ master merges with remote master
+ Local ref configured for 'git push':
+ master pushes to master (up to date)
diff --git a/tests.branching/init-cwd.script b/tests.branching/init-cwd.script
new file mode 100755
index 00000000..91c23506
--- /dev/null
+++ b/tests.branching/init-cwd.script
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+set -eu
+
+# Test that "morph init" works for the current working directory.
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init .
+"$SRCDIR/scripts/list-tree" "$DATADIR/mine"
+
diff --git a/tests.branching/init-cwd.stdout b/tests.branching/init-cwd.stdout
new file mode 100644
index 00000000..e7922ee1
--- /dev/null
+++ b/tests.branching/init-cwd.stdout
@@ -0,0 +1,2 @@
+d .
+d ./.morph
diff --git a/tests.branching/init-default.script b/tests.branching/init-default.script
new file mode 100755
index 00000000..5ec5148d
--- /dev/null
+++ b/tests.branching/init-default.script
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+set -eu
+
+# Test that "morph init" works without an explicit argument.
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/list-tree" "$DATADIR/mine"
diff --git a/tests.branching/init-default.stdout b/tests.branching/init-default.stdout
new file mode 100644
index 00000000..e7922ee1
--- /dev/null
+++ b/tests.branching/init-default.stdout
@@ -0,0 +1,2 @@
+d .
+d ./.morph
diff --git a/tests.branching/init-existing.script b/tests.branching/init-existing.script
new file mode 100755
index 00000000..59b658aa
--- /dev/null
+++ b/tests.branching/init-existing.script
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+set -eu
+
+# Test that "morph init" works when given the name of an existing,
+# empty directory.
+
+"$SRCDIR/scripts/test-morph" init "$DATADIR/mine"
+"$SRCDIR/scripts/list-tree" "$DATADIR/mine"
diff --git a/tests.branching/init-existing.stdout b/tests.branching/init-existing.stdout
new file mode 100644
index 00000000..e7922ee1
--- /dev/null
+++ b/tests.branching/init-existing.stdout
@@ -0,0 +1,2 @@
+d .
+d ./.morph
diff --git a/tests.branching/init-newdir.exit b/tests.branching/init-newdir.exit
new file mode 100644
index 00000000..d00491fd
--- /dev/null
+++ b/tests.branching/init-newdir.exit
@@ -0,0 +1 @@
+1
diff --git a/tests.branching/init-newdir.script b/tests.branching/init-newdir.script
new file mode 100755
index 00000000..3bb4a1ba
--- /dev/null
+++ b/tests.branching/init-newdir.script
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+set -eu
+
+# Test that "morph init" gives an error when given the name of a
+# directory that does not exist yet.
+
+"$SRCDIR/scripts/test-morph" init "$DATADIR/foo"
+
diff --git a/tests.branching/init-newdir.stderr b/tests.branching/init-newdir.stderr
new file mode 100644
index 00000000..066d69c0
--- /dev/null
+++ b/tests.branching/init-newdir.stderr
@@ -0,0 +1 @@
+ERROR: can only initialize an existing empty directory: TMP/foo
diff --git a/tests.branching/init-nonempty.exit b/tests.branching/init-nonempty.exit
new file mode 100644
index 00000000..d00491fd
--- /dev/null
+++ b/tests.branching/init-nonempty.exit
@@ -0,0 +1 @@
+1
diff --git a/tests.branching/init-nonempty.script b/tests.branching/init-nonempty.script
new file mode 100755
index 00000000..28762d08
--- /dev/null
+++ b/tests.branching/init-nonempty.script
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+set -eu
+
+# Test that "morph init" fails when given the name of an existing,
+# non-empty directory.
+
+touch "$DATADIR/mine/foo"
+"$SRCDIR/scripts/test-morph" init "$DATADIR/mine"
diff --git a/tests.branching/init-nonempty.stderr b/tests.branching/init-nonempty.stderr
new file mode 100644
index 00000000..4ed4bba8
--- /dev/null
+++ b/tests.branching/init-nonempty.stderr
@@ -0,0 +1 @@
+ERROR: can only initialize empty directory: TMP/mine
diff --git a/tests.branching/merge-explicitly-named-repos.script b/tests.branching/merge-explicitly-named-repos.script
new file mode 100755
index 00000000..de6ecebc
--- /dev/null
+++ b/tests.branching/merge-explicitly-named-repos.script
@@ -0,0 +1,48 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Check that "morph merge" merges explicitly named repositories.
+
+
+set -eu
+
+
+# Create system branch.
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" branch newbranch
+
+# Make a change to a chunk.
+cd newbranch/morphs
+"$SRCDIR/scripts/test-morph" edit hello master
+cd ../hello
+touch newfile.txt
+git add newfile.txt
+git commit -m foo --quiet
+
+# Merge changes to a new system branch.
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" branch otherbranch
+cd otherbranch/morphs
+"$SRCDIR/scripts/test-morph" edit hello master
+"$SRCDIR/scripts/test-morph" merge newbranch hello
+
+# Check results.
+cd ../hello
+git status --short # make sure all changes are committed
+ls newfile.txt # make sure the new file is there
+
diff --git a/tests.branching/merge-explicitly-named-repos.stdout b/tests.branching/merge-explicitly-named-repos.stdout
new file mode 100644
index 00000000..cd2122c4
--- /dev/null
+++ b/tests.branching/merge-explicitly-named-repos.stdout
@@ -0,0 +1 @@
+newfile.txt
diff --git a/tests.branching/minedir-not-found.exit b/tests.branching/minedir-not-found.exit
new file mode 100644
index 00000000..d00491fd
--- /dev/null
+++ b/tests.branching/minedir-not-found.exit
@@ -0,0 +1 @@
+1
diff --git a/tests.branching/minedir-not-found.script b/tests.branching/minedir-not-found.script
new file mode 100755
index 00000000..eb8c9d51
--- /dev/null
+++ b/tests.branching/minedir-not-found.script
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+scripts/test-morph init "$DATADIR/mine"
+cd "$DATADIR"
+"$SRCDIR/scripts/test-morph" minedir
diff --git a/tests.branching/minedir-not-found.stderr b/tests.branching/minedir-not-found.stderr
new file mode 100644
index 00000000..46dbd84b
--- /dev/null
+++ b/tests.branching/minedir-not-found.stderr
@@ -0,0 +1 @@
+ERROR: Can't find the mine directory
diff --git a/tests.branching/minedir.script b/tests.branching/minedir.script
new file mode 100755
index 00000000..5f3574b7
--- /dev/null
+++ b/tests.branching/minedir.script
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+scripts/test-morph init "$DATADIR/mine"
+mkdir -p "$DATADIR/mine/a/b/c"
+cd "$DATADIR/mine/a/b/c"
+"$SRCDIR/scripts/test-morph" minedir
diff --git a/tests.branching/minedir.stdout b/tests.branching/minedir.stdout
new file mode 100644
index 00000000..0fc25ee9
--- /dev/null
+++ b/tests.branching/minedir.stdout
@@ -0,0 +1 @@
+TMP/mine
diff --git a/tests.branching/setup b/tests.branching/setup
new file mode 100755
index 00000000..68117fa3
--- /dev/null
+++ b/tests.branching/setup
@@ -0,0 +1,99 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Set up $DATADIR.
+#
+# - a morph.conf configuration file
+# - an empty morph mine directory
+# - a git repository called "morphs" for fake system, stratum morphologies
+# - a git repository calle "hello" for a dummy chunk
+
+set -eu
+
+
+# Create a morph configuration file
+cat <<EOF > "$DATADIR/morph.conf"
+[config]
+git-base-url = file://$DATADIR/
+cachedir = $DATADIR/mine/.morph/cache
+log = $DATADIR/morph.log
+keep-path = true
+no-distcc = true
+EOF
+
+
+# Create an empty directory to be used as a morph mine
+mkdir "$DATADIR/mine"
+
+
+# Create a fake morphs repository
+mkdir "$DATADIR/morphs"
+
+cat <<EOF > "$DATADIR/morphs/hello-system.chunk"
+{
+ "name": "hello-system",
+ "kind": "system",
+ "disk-size": "1G",
+ "strata": [
+ "hello-stratum"
+ ]
+}
+EOF
+
+cat <<EOF > "$DATADIR/morphs/hello-stratum.chunk"
+{
+ "name": "hello-stratum",
+ "kind": "stratum",
+ "sources": [
+ {
+ "name": "hello",
+ "ref": "master"
+ }
+ ]
+}
+EOF
+
+scripts/run-git-in "$DATADIR/morphs" init
+scripts/run-git-in "$DATADIR/morphs" add .
+scripts/run-git-in "$DATADIR/morphs" commit -m initial
+
+
+# Add an extra branch to the morphs repo.
+scripts/run-git-in "$DATADIR/morphs" checkout -b alfred
+scripts/run-git-in "$DATADIR/morphs" checkout master
+
+
+# Create a dummy chunk repository
+mkdir "$DATADIR/hello"
+
+cat <<EOF > "$DATADIR/hello/hello.chunk"
+{
+ "name": "hello",
+ "kind": "chunk",
+ "build-system": "dummy"
+}
+EOF
+
+scripts/run-git-in "$DATADIR/hello" init
+scripts/run-git-in "$DATADIR/hello" add .
+scripts/run-git-in "$DATADIR/hello" commit -m initial
+
+
+# Add an extra branch to the hello repo.
+scripts/run-git-in "$DATADIR/hello" checkout -b alfred
+scripts/run-git-in "$DATADIR/hello" checkout master
+
diff --git a/tests.branching/show-system-branch-shows-name-correctly.script b/tests.branching/show-system-branch-shows-name-correctly.script
new file mode 100755
index 00000000..38b2aa14
--- /dev/null
+++ b/tests.branching/show-system-branch-shows-name-correctly.script
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Check that 'morph show-system-branch' shows the name of the current system
+# branch correctly.
+
+
+set -eu
+
+# Create system branch.
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" branch newbranch
+
+cd newbranch/morphs
+"$SRCDIR/scripts/test-morph" show-system-branch
+
diff --git a/tests.branching/show-system-branch-shows-name-correctly.stdout b/tests.branching/show-system-branch-shows-name-correctly.stdout
new file mode 100644
index 00000000..467e4889
--- /dev/null
+++ b/tests.branching/show-system-branch-shows-name-correctly.stdout
@@ -0,0 +1 @@
+newbranch
diff --git a/tests.branching/teardown b/tests.branching/teardown
new file mode 100755
index 00000000..94928416
--- /dev/null
+++ b/tests.branching/teardown
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Clean up $DATADIR.
+
+set -eu
+
+find "$DATADIR" -mindepth 1 -delete
diff --git a/tests.branching/workflow.script b/tests.branching/workflow.script
new file mode 100755
index 00000000..d2ce374f
--- /dev/null
+++ b/tests.branching/workflow.script
@@ -0,0 +1,39 @@
+#!/bin/sh
+# Copyright (C) 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
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# Do a complete workflow test with Morph branching and merging.
+
+
+set -eu
+
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" branch me/readme-fix
+cd me/readme-fix/morphs
+"$SRCDIR/scripts/test-morph" edit hello master
+cd ../hello
+echo > README yoyoyo
+git add README
+git commit -m "Fix README, yo!" --quiet
+
+cd "$DATADIR/mine"
+"$SRCDIR/scripts/test-morph" checkout master
+cd master/morphs
+"$SRCDIR/scripts/test-morph" edit hello master
+"$SRCDIR/scripts/test-morph" merge me/readme-fix hello
+
diff --git a/tests/missing-ref.stderr b/tests/missing-ref.stderr
index 5a1ffb2a..748a891c 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 file:///chunk-repo
diff --git a/tests/show-dependencies.script b/tests/show-dependencies.script
index 78fb186b..4b28078f 100755
--- a/tests/show-dependencies.script
+++ b/tests/show-dependencies.script
@@ -20,5 +20,5 @@
set -eu
-tests/morph show-dependencies test-repo master xfce-core.morph
-
+tests/morph show-dependencies test-repo master xfce-core.morph | \
+ sed "s,$DATADIR,TMP,g"
diff --git a/tests/show-dependencies.stdout b/tests/show-dependencies.stdout
index 5ad63c65..94ae5749 100644
--- a/tests/show-dependencies.stdout
+++ b/tests/show-dependencies.stdout
@@ -1,133 +1,133 @@
dependency tree:
- 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
+ file://TMP/test-repo|refs/remotes/origin/master|cairo.morph
+ file://TMP/test-repo|refs/remotes/origin/master|dbus-glib.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|dbus.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|glib.morph
+ file://TMP/test-repo|refs/remotes/origin/master|dbus.morph
+ file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4util.morph
+ file://TMP/test-repo|refs/remotes/origin/master|fontconfig.morph
+ file://TMP/test-repo|refs/remotes/origin/master|freetype.morph
+ file://TMP/test-repo|refs/remotes/origin/master|garcon.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4util.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|glib.morph
+ file://TMP/test-repo|refs/remotes/origin/master|glib.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|cairo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|dbus-glib.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|dbus.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|fontconfig.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|freetype.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|glib.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|pango.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gtk-xfce-engine.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|garcon.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gtk.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|cairo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|glib.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|pango.morph
+ file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|libxfce4util.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ file://TMP/test-repo|refs/remotes/origin/master|pango.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|fontconfig.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|freetype.morph
+ file://TMP/test-repo|refs/remotes/origin/master|thunar.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ file://TMP/test-repo|refs/remotes/origin/master|tumbler.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce-core.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|garcon.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-xfce-engine.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4util.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|thunar.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|tumbler.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfce4-appfinder.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfce4-panel.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfce4-session.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfce4-settings.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfdesktop.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfwm4.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-appfinder.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|garcon.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-panel.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|garcon.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-session.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-settings.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4util.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfdesktop.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfwm4.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ -> file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
build order:
group:
- 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
+ file://TMP/test-repo|refs/remotes/origin/master|cairo.morph
+ file://TMP/test-repo|refs/remotes/origin/master|dbus.morph
+ file://TMP/test-repo|refs/remotes/origin/master|fontconfig.morph
+ file://TMP/test-repo|refs/remotes/origin/master|freetype.morph
+ file://TMP/test-repo|refs/remotes/origin/master|glib.morph
group:
- 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
+ file://TMP/test-repo|refs/remotes/origin/master|dbus-glib.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gdk-pixbuf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|pango.morph
group:
- test-repo|refs/remotes/origin/master|gtk.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gtk.morph
group:
- test-repo|refs/remotes/origin/master|gtk-stack.morph
+ file://TMP/test-repo|refs/remotes/origin/master|gtk-stack.morph
group:
- test-repo|refs/remotes/origin/master|libxfce4util.morph
- test-repo|refs/remotes/origin/master|tumbler.morph
+ file://TMP/test-repo|refs/remotes/origin/master|libxfce4util.morph
+ file://TMP/test-repo|refs/remotes/origin/master|tumbler.morph
group:
- test-repo|refs/remotes/origin/master|exo.morph
- test-repo|refs/remotes/origin/master|garcon.morph
- test-repo|refs/remotes/origin/master|xfconf.morph
+ file://TMP/test-repo|refs/remotes/origin/master|exo.morph
+ file://TMP/test-repo|refs/remotes/origin/master|garcon.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfconf.morph
group:
- test-repo|refs/remotes/origin/master|libxfce4ui.morph
+ file://TMP/test-repo|refs/remotes/origin/master|libxfce4ui.morph
group:
- 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
+ file://TMP/test-repo|refs/remotes/origin/master|gtk-xfce-engine.morph
+ file://TMP/test-repo|refs/remotes/origin/master|thunar.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-appfinder.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-panel.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-session.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce4-settings.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfdesktop.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfwm4.morph
group:
- test-repo|refs/remotes/origin/master|xfce-core.morph
+ file://TMP/test-repo|refs/remotes/origin/master|xfce-core.morph