+"""Invirt build utilities.
+
+This module contains utility functions used by both the invirtibuilder
+and the remctl submission scripts that insert items into its queue.
+"""
+
+
+import os
+
+from debian_bundle import changelog
+from debian_bundle import deb822
+
+import invirt.common as c
+from invirt.config import structs as config
+
+
+_QUEUE_DIR = '/var/lib/invirt-dev/queue'
+_REPO_DIR = '/srv/git'
+_LOG_DIR = '/var/log/invirt/builds'
+_HOOKS_DIR = '/usr/share/invirt-dev/build.d'
+
+
+class InvalidBuild(ValueError):
+ pass
+
+
+def getRepo(package):
+ """Return the path to the git repo for a given package."""
+ return os.path.join(_REPO_DIR, 'packages', '%s.git' % package)
+
+
+def pocketToGit(pocket):
+ """Map a pocket in the configuration to a git branch."""
+ return config.git.pockets[pocket].get('git', pocket)
+
+
+def pocketToApt(pocket):
+ """Map a pocket in the configuration to an apt repo pocket."""
+ return config.git.pockets[pocket].get('apt', pocket)
+
+
+def getGitFile(package, ref, path):
+ """Return the contents of a path from a git ref in a package."""
+ return c.captureOutput(['git', 'cat-file', 'blob', '%s:%s' % (ref, path)],
+ cwd=getRepo(package))
+
+
+def getChangelog(package, ref):
+ """Get a changelog object for a given ref in a given package.
+
+ This returns a debian_bundle.changelog.Changelog object for a
+ given ref of a given package.
+ """
+ return changelog.Changelog(getGitFile(package, ref, 'debian/changelog'))
+
+
+def getVersion(package, ref):
+ """Get the version of a given package at a particular ref."""
+ return getChangelog(package, ref).get_version()
+
+
+def validateBuild(pocket, package, commit):
+ """Given the parameters of a new build, validate that build.
+
+ The checks this function performs vary based on whether or not the
+ pocket is configured with allow_backtracking.
+
+ A build of a pocket without allow_backtracking set must be a
+ fast-forward of the previous revision, and the most recent version
+ in the changelog most be strictly greater than the version
+ currently in the repository.
+
+ In all cases, this revision of the package can only have the same
+ version number as any other revision currently in the apt
+ repository if they have the same commit ID.
+
+ If it's unspecified, it is assumed that pocket do not
+ allow_backtracking.
+
+ If this build request fails validation, this function will raise a
+ InvalidBuild exception, with information about why the validation
+ failed.
+
+ If this build request can be satisfied by copying the package from
+ another pocket, then this function returns that pocket. Otherwise,
+ it returns True.
+ """
+ package_repo = getRepo(package)
+ new_version = getVersion(package, commit)
+
+ for p in config.git.pockets:
+ if p == pocket:
+ continue
+
+ b = pocketToGit(p)
+ current_commit = c.captureOutput(['git', 'rev-parse', b],
+ cwd=package_repo)
+ current_version = getVersion(package, b)
+
+ if current_version == new_version:
+ if current_commit == commit:
+ return p
+ else:
+ raise InvalidBuild('Version %s of %s already available in '
+ 'pocket %s from commit %s' %
+ (new_version, package, p, current_commit))
+
+ if config.git.pockets[pocket].get('allow_backtracking', False):
+ branch = pocketToGit(pocket)
+ current_version = getVersion(package, branch)
+ if new_version <= current_version:
+ raise InvalidBuild('New version %s of %s is not newer than '
+ 'version %s currently in pocket %s' %
+ (new_version, package, current_version, pocket))
+
+ # Almost by definition, A is a fast-forward of B if B..A is
+ # empty
+ if not c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)]):
+ raise InvalidBuild('New commit %s of %s is not a fast-forward of'
+ 'commit currently in pocket %s' %
+ (commit, package, pocket))
+
+