repos: Introduce commit and branch and alternative to refspec key

This allows to clearly express if a refspec is a commit ID or a
branch/symbolic reference. Mixing repos with old refspec with repos that
uses new commit/branch is supported, but not mixing the keys in the same
repo.

This commit lays the ground by extending the schema and ensuring that
the code which at minimum needs to know about these new keys does so.
Existing tests still pass, new ones will follow.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
This commit is contained in:
Jan Kiszka 2023-05-30 16:55:07 +02:00
parent 51846faef4
commit bb48adad1f
2 changed files with 89 additions and 43 deletions

View File

@ -76,11 +76,13 @@ class Repo:
Represents a repository in the kas configuration. Represents a repository in the kas configuration.
""" """
def __init__(self, name, url, path, refspec, layers, patches, def __init__(self, name, url, path, commit, branch, refspec, layers,
disable_operations): patches, disable_operations):
self.name = name self.name = name
self.url = url self.url = url
self.path = path self.path = path
self.commit = commit
self.branch = branch
self.refspec = refspec self.refspec = refspec
self._layers = layers self._layers = layers
self._patches = patches self._patches = patches
@ -109,19 +111,26 @@ class Repo:
continue continue
return self.url return self.url
elif item == 'revision': elif item == 'revision':
if not self.refspec: if self.commit:
return self.commit
branch = self.branch or self.refspec
if not branch:
return None return None
(_, output) = run_cmd(self.resolve_branch_cmd(), (_, output) = run_cmd(self.resolve_branch_cmd(),
cwd=self.path, fail=False) cwd=self.path, fail=False)
if output: if output:
return output.strip() return output.strip()
return self.refspec return branch
# Default behaviour # Default behaviour
raise AttributeError raise AttributeError
def __str__(self): def __str__(self):
return '%s:%s %s %s' % (self.url, self.refspec, if self.commit and self.branch:
return '%s:%s(%s) %s %s' % (self.url, self.commit, self.branch,
self.path, self._layers)
return '%s:%s %s %s' % (self.url,
self.commit or self.branch or self.refspec,
self.path, self._layers) self.path, self._layers)
@staticmethod @staticmethod
@ -156,13 +165,23 @@ class Repo:
url = repo_config.get('url', None) url = repo_config.get('url', None)
name = repo_config.get('name', name) name = repo_config.get('name', name)
typ = repo_config.get('type', 'git') typ = repo_config.get('type', 'git')
refspec = repo_overrides.get('refspec', repo_config.get('refspec', commit = repo_config.get('commit', None)
repo_defaults.get('refspec', None))) branch = repo_config.get('branch', repo_defaults.get('branch', None))
if refspec is None and url is not None: refspec = repo_config.get('refspec',
raise RepoRefError('No refspec specified for repository "{}". ' repo_defaults.get('refspec', None))
'This is only allowed for local repositories.' if commit is None and branch is None and refspec is None \
.format(name)) and url is not None:
raise RepoRefError('No commit or branch specified for repository '
'"{}". This is only allowed for local '
'repositories.'.format(name))
if refspec is None:
commit = repo_overrides.get('refspec', commit)
else:
if commit is not None or branch is not None:
raise RepoRefError('Unsupported mixture of legacy refspec '
'and commit/branch for repository "{}"'
.format(name))
refspec = repo_overrides.get('refspec', refspec)
path = repo_config.get('path', None) path = repo_config.get('path', None)
disable_operations = False disable_operations = False
@ -183,11 +202,11 @@ class Repo:
disable_operations = True disable_operations = True
if typ == 'git': if typ == 'git':
return GitRepo(name, url, path, refspec, layers, patches, return GitRepo(name, url, path, commit, branch, refspec, layers,
disable_operations) patches, disable_operations)
if typ == 'hg': if typ == 'hg':
return MercurialRepo(name, url, path, refspec, layers, patches, return MercurialRepo(name, url, path, commit, branch, refspec,
disable_operations) layers, patches, disable_operations)
raise UnsupportedRepoTypeError('Repo type "%s" not supported.' % typ) raise UnsupportedRepoTypeError('Repo type "%s" not supported.' % typ)
@staticmethod @staticmethod
@ -263,21 +282,25 @@ class RepoImpl(Repo):
'the remote url.') 'the remote url.')
# take what came out of clone and stick to that forever # take what came out of clone and stick to that forever
if self.refspec is None: if self.commit is None and self.branch is None \
and self.refspec is None:
return 0 return 0
if not get_context().update: if not get_context().update:
# Does refspec exist in the current repository? # Do commit/branch/refspec exist in the current repository?
(retc, output) = await run_cmd_async(self.contains_refspec_cmd(), (retc, output) = await run_cmd_async(self.contains_refspec_cmd(),
cwd=self.path, cwd=self.path,
fail=False, fail=False,
liveupdate=False) liveupdate=False)
if retc == 0: if retc == 0:
logging.info('Repository %s already contains %s as %s', logging.info('Repository %s already contains %s as %s',
self.name, self.refspec, output.strip()) self.name,
self.commit or self.branch or self.refspec,
output.strip())
return retc return retc
# Try to fetch if refspec is missing or if --update argument was passed # Try to fetch if commit/branch/refspec is missing or if --update
# argument was passed
(retc, output) = await run_cmd_async(self.fetch_cmd(), (retc, output) = await run_cmd_async(self.fetch_cmd(),
cwd=self.path, cwd=self.path,
fail=False) fail=False)
@ -292,7 +315,9 @@ class RepoImpl(Repo):
""" """
Checks out the correct revision of the repo. Checks out the correct revision of the repo.
""" """
if self.operations_disabled or self.refspec is None: if self.operations_disabled \
or (self.commit is None and self.branch is None
and self.refspec is None):
return return
if not get_context().force_checkout: if not get_context().force_checkout:
@ -304,14 +329,22 @@ class RepoImpl(Repo):
logging.warning('Repo %s is dirty - no checkout', self.name) logging.warning('Repo %s is dirty - no checkout', self.name)
return return
(_, output) = run_cmd(self.resolve_branch_cmd(), if self.commit:
cwd=self.path, fail=False) desired_ref = self.commit
if output:
desired_ref = output.strip()
is_branch = True
else:
desired_ref = self.refspec
is_branch = False is_branch = False
else:
(_, output) = run_cmd(self.resolve_branch_cmd(),
cwd=self.path, fail=False)
if output:
desired_ref = output.strip()
is_branch = True
elif self.branch:
raise RepoRefError(
'Branch "{}" cannot be found in repository {}'
.format(self.branch, self.name))
else:
desired_ref = self.refspec
is_branch = False
run_cmd(self.checkout_cmd(desired_ref, is_branch), cwd=self.path) run_cmd(self.checkout_cmd(desired_ref, is_branch), cwd=self.path)
@ -398,9 +431,9 @@ class GitRepo(RepoImpl):
Provides the git functionality for a Repo. Provides the git functionality for a Repo.
""" """
def remove_ref_prefix(self, refspec): def remove_ref_prefix(self, branch):
ref_prefix = 'refs/' ref_prefix = 'refs/'
return refspec[refspec.startswith(ref_prefix) and len(ref_prefix):] return branch[branch.startswith(ref_prefix) and len(ref_prefix):]
def add_cmd(self): def add_cmd(self):
return ['git', 'add', '-A'] return ['git', 'add', '-A']
@ -420,18 +453,19 @@ class GitRepo(RepoImpl):
'-m', 'msg'] '-m', 'msg']
def contains_refspec_cmd(self): def contains_refspec_cmd(self):
refspec = self.refspec branch = self.branch or self.refspec
if refspec and refspec.startswith('refs/'): if branch and branch.startswith('refs/'):
refspec = 'remotes/origin/' + self.remove_ref_prefix(refspec) branch = 'remotes/origin/' + self.remove_ref_prefix(branch)
return ['git', 'cat-file', '-t', refspec] return ['git', 'cat-file', '-t', self.commit or branch]
def fetch_cmd(self): def fetch_cmd(self):
cmd = ['git', 'fetch', '-q'] cmd = ['git', 'fetch', '-q']
if self.refspec.startswith('refs/'): branch = self.branch or self.refspec
if branch and branch.startswith('refs/'):
cmd.extend(['origin', cmd.extend(['origin',
'+' + self.refspec '+' + branch
+ ':refs/remotes/origin/' + ':refs/remotes/origin/'
+ self.remove_ref_prefix(self.refspec)]) + self.remove_ref_prefix(branch)])
return cmd return cmd
@ -440,13 +474,14 @@ class GitRepo(RepoImpl):
def resolve_branch_cmd(self): def resolve_branch_cmd(self):
return ['git', 'rev-parse', '--verify', '-q', return ['git', 'rev-parse', '--verify', '-q',
'origin/{refspec}'. 'origin/{branch}'.
format(refspec=self.remove_ref_prefix(self.refspec))] format(branch=self.remove_ref_prefix(
self.branch or self.refspec))]
def checkout_cmd(self, desired_ref, is_branch): def checkout_cmd(self, desired_ref, is_branch):
cmd = ['git', 'checkout', '-q', self.remove_ref_prefix(desired_ref)] cmd = ['git', 'checkout', '-q', self.remove_ref_prefix(desired_ref)]
if is_branch: if is_branch:
branch = self.remove_ref_prefix(self.refspec) branch = self.remove_ref_prefix(self.branch or self.refspec)
branch = branch[branch.startswith('heads/') and len('heads/'):] branch = branch[branch.startswith('heads/') and len('heads/'):]
cmd.extend(['-B', branch]) cmd.extend(['-B', branch])
if get_context().force_checkout: if get_context().force_checkout:
@ -454,9 +489,10 @@ class GitRepo(RepoImpl):
return cmd return cmd
def prepare_patches_cmd(self): def prepare_patches_cmd(self):
branch = self.branch or self.refspec
return ['git', 'checkout', '-q', '-B', return ['git', 'checkout', '-q', '-B',
'patched-{refspec}'. 'patched-{refspec}'.
format(refspec=self.remove_ref_prefix(self.refspec))] format(refspec=self.commit or self.remove_ref_prefix(branch))]
def apply_patches_file_cmd(self, path): def apply_patches_file_cmd(self, path):
return ['git', 'apply', '--whitespace=nowarn', path] return ['git', 'apply', '--whitespace=nowarn', path]
@ -483,7 +519,7 @@ class MercurialRepo(RepoImpl):
return ['hg', 'commit', '--user', 'kas <kas@example.com>', '-m', 'msg'] return ['hg', 'commit', '--user', 'kas <kas@example.com>', '-m', 'msg']
def contains_refspec_cmd(self): def contains_refspec_cmd(self):
return ['hg', 'log', '-r', self.refspec] return ['hg', 'log', '-r', self.commit or self.branch or self.refspec]
def fetch_cmd(self): def fetch_cmd(self):
return ['hg', 'pull'] return ['hg', 'pull']
@ -502,8 +538,9 @@ class MercurialRepo(RepoImpl):
return cmd return cmd
def prepare_patches_cmd(self): def prepare_patches_cmd(self):
refspec = self.commit or self.branch or self.refspec
return ['hg', 'branch', '-f', return ['hg', 'branch', '-f',
'patched-{refspec}'.format(refspec=self.refspec)] 'patched-{refspec}'.format(refspec=refspec)]
def apply_patches_file_cmd(self, path): def apply_patches_file_cmd(self, path):
return ['hg', 'import', '--no-commit', path] return ['hg', 'import', '--no-commit', path]

View File

@ -73,6 +73,9 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"branch": {
"type": "string"
},
"refspec": { "refspec": {
"type": "string" "type": "string"
}, },
@ -159,6 +162,12 @@
"type": { "type": {
"type": "string" "type": "string"
}, },
"commit": {
"type": "string"
},
"branch": {
"type": "string"
},
"refspec": { "refspec": {
"type": "string" "type": "string"
}, },