mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-28 20:17:26 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
6a872c9dae | |||
df6c506a8a | |||
febe73ff16 | |||
e5670c8812 | |||
48b2d10d8f | |||
0588f3dc52 | |||
1bb4fb222d |
@ -29,6 +29,7 @@ following DTD:
|
||||
project*,
|
||||
extend-project*,
|
||||
repo-hooks?,
|
||||
superproject?,
|
||||
include*)>
|
||||
|
||||
<!ELEMENT notice (#PCDATA)>
|
||||
@ -98,12 +99,22 @@ following DTD:
|
||||
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
|
||||
<!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
|
||||
|
||||
<!ELEMENT superproject (EMPTY)>
|
||||
<!ATTLIST superproject name CDATA #REQUIRED>
|
||||
<!ATTLIST superproject remote IDREF #IMPLIED>
|
||||
|
||||
<!ELEMENT include EMPTY>
|
||||
<!ATTLIST include name CDATA #REQUIRED>
|
||||
<!ATTLIST include groups CDATA #IMPLIED>
|
||||
]>
|
||||
```
|
||||
|
||||
For compatibility purposes across repo releases, all unknown elements are
|
||||
silently ignored. However, repo reserves all possible names for itself for
|
||||
future use. If you want to use custom elements, the `x-*` namespace is
|
||||
reserved for that purpose, and repo guarantees to never allocate any
|
||||
corresponding names.
|
||||
|
||||
A description of the elements and their attributes follows.
|
||||
|
||||
|
||||
@ -377,6 +388,28 @@ defined `project` element.
|
||||
|
||||
Attribute `enabled-list`: List of hooks to use, whitespace or comma separated.
|
||||
|
||||
### Element superproject
|
||||
|
||||
***
|
||||
*Note*: This is currently a WIP.
|
||||
***
|
||||
|
||||
NB: See the [git superprojects documentation](
|
||||
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects) for background
|
||||
information.
|
||||
|
||||
This element is used to specify the URL of the superproject. It has "name" and
|
||||
"remote" as atrributes. Only "name" is required while the others have
|
||||
reasonable defaults. At most one superproject may be specified.
|
||||
Attempting to redefine it will fail to parse.
|
||||
|
||||
Attribute `name`: A unique name for the superproject. This attribute has the
|
||||
same meaning as project's name attribute. See the
|
||||
[element project](#element-project) for more information.
|
||||
|
||||
Attribute `remote`: Name of a previously defined remote element.
|
||||
If not supplied the remote given by the default element is used.
|
||||
|
||||
### Element include
|
||||
|
||||
This element provides the capability of including another manifest
|
||||
|
4
error.py
4
error.py
@ -13,6 +13,10 @@
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# URL to file bug reports for repo tool issues.
|
||||
BUG_REPORT_URL = 'https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue'
|
||||
|
||||
|
||||
class ManifestParseError(Exception):
|
||||
"""Failed to parse the manifest file.
|
||||
"""
|
||||
|
149
git_superproject.py
Normal file
149
git_superproject.py
Normal file
@ -0,0 +1,149 @@
|
||||
# Copyright (C) 2021 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Provide functionality to get all projects and their SHAs from Superproject.
|
||||
|
||||
For more information on superproject, check out:
|
||||
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
|
||||
|
||||
Examples:
|
||||
superproject = Superproject()
|
||||
project_shas = superproject.GetAllProjectsSHAs()
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from error import GitError
|
||||
from git_command import GitCommand
|
||||
import platform_utils
|
||||
|
||||
|
||||
class Superproject(object):
|
||||
"""Get SHAs from superproject.
|
||||
|
||||
It does a 'git clone' of superproject and 'git ls-tree' to get list of SHAs for all projects.
|
||||
It contains project_shas which is a dictionary with project/sha entries.
|
||||
"""
|
||||
def __init__(self, repodir, superproject_dir='exp-superproject'):
|
||||
"""Initializes superproject.
|
||||
|
||||
Args:
|
||||
repodir: Path to the .repo/ dir for holding all internal checkout state.
|
||||
superproject_dir: Relative path under |repodir| to checkout superproject.
|
||||
"""
|
||||
self._project_shas = None
|
||||
self._repodir = os.path.abspath(repodir)
|
||||
self._superproject_dir = superproject_dir
|
||||
self._superproject_path = os.path.join(self._repodir, superproject_dir)
|
||||
|
||||
@property
|
||||
def project_shas(self):
|
||||
"""Returns a dictionary of projects and their SHAs."""
|
||||
return self._project_shas
|
||||
|
||||
def _Clone(self, url, branch=None):
|
||||
"""Do a 'git clone' for the given url and branch.
|
||||
|
||||
Args:
|
||||
url: superproject's url to be passed to git clone.
|
||||
branch: the branchname to be passed as argument to git clone.
|
||||
|
||||
Returns:
|
||||
True if 'git clone <url> <branch>' is successful, or False.
|
||||
"""
|
||||
cmd = ['clone', url, '--depth', '1']
|
||||
if branch:
|
||||
cmd += ['--branch', branch]
|
||||
p = GitCommand(None,
|
||||
cmd,
|
||||
cwd=self._superproject_path,
|
||||
capture_stdout=True,
|
||||
capture_stderr=True)
|
||||
retval = p.Wait()
|
||||
if retval:
|
||||
# `git clone` is documented to produce an exit status of `128` if
|
||||
# the requested url or branch are not present in the configuration.
|
||||
print('repo: error: git clone call failed with return code: %r, stderr: %r' %
|
||||
(retval, p.stderr), file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _LsTree(self):
|
||||
"""Returns the data from 'git ls-tree -r HEAD'.
|
||||
|
||||
Works only in git repositories.
|
||||
|
||||
Returns:
|
||||
data: data returned from 'git ls-tree -r HEAD' instead of None.
|
||||
"""
|
||||
git_dir = os.path.join(self._superproject_path, 'superproject')
|
||||
if not os.path.exists(git_dir):
|
||||
raise GitError('git ls-tree. Missing drectory: %s' % git_dir)
|
||||
data = None
|
||||
cmd = ['ls-tree', '-z', '-r', 'HEAD']
|
||||
p = GitCommand(None,
|
||||
cmd,
|
||||
cwd=git_dir,
|
||||
capture_stdout=True,
|
||||
capture_stderr=True)
|
||||
retval = p.Wait()
|
||||
if retval == 0:
|
||||
data = p.stdout
|
||||
else:
|
||||
# `git clone` is documented to produce an exit status of `128` if
|
||||
# the requested url or branch are not present in the configuration.
|
||||
print('repo: error: git ls-tree call failed with return code: %r, stderr: %r' % (
|
||||
retval, p.stderr), file=sys.stderr)
|
||||
return data
|
||||
|
||||
def GetAllProjectsSHAs(self, url, branch=None):
|
||||
"""Get SHAs for all projects from superproject and save them in _project_shas.
|
||||
|
||||
Args:
|
||||
url: superproject's url to be passed to git clone.
|
||||
branch: the branchname to be passed as argument to git clone.
|
||||
|
||||
Returns:
|
||||
A dictionary with the projects/SHAs instead of None.
|
||||
"""
|
||||
if not url:
|
||||
raise ValueError('url argument is not supplied.')
|
||||
if os.path.exists(self._superproject_path):
|
||||
platform_utils.rmtree(self._superproject_path)
|
||||
os.mkdir(self._superproject_path)
|
||||
|
||||
# TODO(rtenneti): we shouldn't be cloning the repo from scratch every time.
|
||||
if not self._Clone(url, branch):
|
||||
raise GitError('git clone failed for url: %s' % url)
|
||||
|
||||
data = self._LsTree()
|
||||
if not data:
|
||||
raise GitError('git ls-tree failed for url: %s' % url)
|
||||
|
||||
# Parse lines like the following to select lines starting with '160000' and
|
||||
# build a dictionary with project path (last element) and its SHA (3rd element).
|
||||
#
|
||||
# 160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00
|
||||
# 120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00
|
||||
shas = {}
|
||||
for line in data.split('\x00'):
|
||||
ls_data = line.split(None, 3)
|
||||
if not ls_data:
|
||||
break
|
||||
if ls_data[0] == '160000':
|
||||
shas[ls_data[3]] = ls_data[2]
|
||||
|
||||
self._project_shas = shas
|
||||
return shas
|
@ -92,7 +92,7 @@ class EventLog(object):
|
||||
def _AddVersionEvent(self):
|
||||
"""Adds a 'version' event at the beginning of current log."""
|
||||
version_event = self._CreateEventDict('version')
|
||||
version_event['evt'] = 2
|
||||
version_event['evt'] = "2"
|
||||
version_event['exe'] = RepoSourceVersion()
|
||||
self._log.insert(0, version_event)
|
||||
|
||||
|
@ -463,6 +463,19 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
' '.join(self._repo_hooks_project.enabled_repo_hooks))
|
||||
root.appendChild(e)
|
||||
|
||||
if self._superproject:
|
||||
root.appendChild(doc.createTextNode(''))
|
||||
e = doc.createElement('superproject')
|
||||
e.setAttribute('name', self._superproject['name'])
|
||||
remoteName = None
|
||||
if d.remote:
|
||||
remoteName = d.remote.name
|
||||
remote = self._superproject.get('remote')
|
||||
if not d.remote or remote.orig_name != remoteName:
|
||||
remoteName = remote.orig_name
|
||||
e.setAttribute('remote', remoteName)
|
||||
root.appendChild(e)
|
||||
|
||||
return doc
|
||||
|
||||
def ToDict(self, **kwargs):
|
||||
@ -473,6 +486,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
'default',
|
||||
'manifest-server',
|
||||
'repo-hooks',
|
||||
'superproject',
|
||||
}
|
||||
# Elements that may be repeated.
|
||||
MULTI_ELEMENTS = {
|
||||
@ -544,6 +558,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
self._Load()
|
||||
return self._repo_hooks_project
|
||||
|
||||
@property
|
||||
def superproject(self):
|
||||
self._Load()
|
||||
return self._superproject
|
||||
|
||||
@property
|
||||
def notice(self):
|
||||
self._Load()
|
||||
@ -591,6 +610,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
self._remotes = {}
|
||||
self._default = None
|
||||
self._repo_hooks_project = None
|
||||
self._superproject = {}
|
||||
self._notice = None
|
||||
self.branch = None
|
||||
self._manifest_server = None
|
||||
@ -793,6 +813,23 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
|
||||
# Store the enabled hooks in the Project object.
|
||||
self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
|
||||
if node.nodeName == 'superproject':
|
||||
name = self._reqatt(node, 'name')
|
||||
# There can only be one superproject.
|
||||
if self._superproject.get('name'):
|
||||
raise ManifestParseError(
|
||||
'duplicate superproject in %s' %
|
||||
(self.manifestFile))
|
||||
self._superproject['name'] = name
|
||||
remote_name = node.getAttribute('remote')
|
||||
if not remote_name:
|
||||
remote = self._default.remote
|
||||
else:
|
||||
remote = self._get_remote(node)
|
||||
if remote is None:
|
||||
raise ManifestParseError("no remote for superproject %s within %s" %
|
||||
(name, self.manifestFile))
|
||||
self._superproject['remote'] = remote.ToRemoteSpec(name)
|
||||
if node.nodeName == 'remove-project':
|
||||
name = self._reqatt(node, 'name')
|
||||
|
||||
|
@ -1197,6 +1197,9 @@ class Project(object):
|
||||
raise ManifestInvalidRevisionError('revision %s in %s not found' %
|
||||
(self.revisionExpr, self.name))
|
||||
|
||||
def SetRevisionId(self, revisionId):
|
||||
self.revisionId = revisionId
|
||||
|
||||
def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
|
||||
"""Perform only the local IO portion of the sync process.
|
||||
Network access is not required.
|
||||
|
91
repo
91
repo
@ -147,7 +147,7 @@ if not REPO_REV:
|
||||
REPO_REV = 'stable'
|
||||
|
||||
# increment this whenever we make important changes to this script
|
||||
VERSION = (2, 11)
|
||||
VERSION = (2, 12)
|
||||
|
||||
# increment this if the MAINTAINER_KEYS block is modified
|
||||
KEYRING_VERSION = (2, 3)
|
||||
@ -246,6 +246,7 @@ GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
|
||||
|
||||
import collections
|
||||
import errno
|
||||
import json
|
||||
import optparse
|
||||
import re
|
||||
import shutil
|
||||
@ -1035,6 +1036,90 @@ def _ParseArguments(args):
|
||||
return cmd, opt, arg
|
||||
|
||||
|
||||
class Requirements(object):
|
||||
"""Helper for checking repo's system requirements."""
|
||||
|
||||
REQUIREMENTS_NAME = 'requirements.json'
|
||||
|
||||
def __init__(self, requirements):
|
||||
"""Initialize.
|
||||
|
||||
Args:
|
||||
requirements: A dictionary of settings.
|
||||
"""
|
||||
self.requirements = requirements
|
||||
|
||||
@classmethod
|
||||
def from_dir(cls, path):
|
||||
return cls.from_file(os.path.join(path, cls.REQUIREMENTS_NAME))
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path):
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
except EnvironmentError:
|
||||
# NB: EnvironmentError is used for Python 2 & 3 compatibility.
|
||||
# If we couldn't open the file, assume it's an old source tree.
|
||||
return None
|
||||
|
||||
return cls.from_data(data)
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
comment_line = re.compile(br'^ *#')
|
||||
strip_data = b''.join(x for x in data.splitlines() if not comment_line.match(x))
|
||||
try:
|
||||
json_data = json.loads(strip_data)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# If we couldn't parse it, assume it's incompatible.
|
||||
return None
|
||||
|
||||
return cls(json_data)
|
||||
|
||||
def _get_soft_ver(self, pkg):
|
||||
"""Return the soft version for |pkg| if it exists."""
|
||||
return self.requirements.get(pkg, {}).get('soft', ())
|
||||
|
||||
def _get_hard_ver(self, pkg):
|
||||
"""Return the hard version for |pkg| if it exists."""
|
||||
return self.requirements.get(pkg, {}).get('hard', ())
|
||||
|
||||
@staticmethod
|
||||
def _format_ver(ver):
|
||||
"""Return a dotted version from |ver|."""
|
||||
return '.'.join(str(x) for x in ver)
|
||||
|
||||
def assert_ver(self, pkg, curr_ver):
|
||||
"""Verify |pkg|'s |curr_ver| is new enough."""
|
||||
curr_ver = tuple(curr_ver)
|
||||
soft_ver = tuple(self._get_soft_ver(pkg))
|
||||
hard_ver = tuple(self._get_hard_ver(pkg))
|
||||
if curr_ver < hard_ver:
|
||||
print('repo: error: Your version of "%s" (%s) is unsupported; '
|
||||
'Please upgrade to at least version %s to continue.' %
|
||||
(pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)),
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if curr_ver < soft_ver:
|
||||
print('repo: warning: Your version of "%s" (%s) is no longer supported; '
|
||||
'Please upgrade to at least version %s to avoid breakage.' %
|
||||
(pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)),
|
||||
file=sys.stderr)
|
||||
|
||||
def assert_all(self):
|
||||
"""Assert all of the requirements are satisified."""
|
||||
# See if we need a repo launcher upgrade first.
|
||||
self.assert_ver('repo', VERSION)
|
||||
|
||||
# Check python before we try to import the repo code.
|
||||
self.assert_ver('python', sys.version_info)
|
||||
|
||||
# Check git while we're at it.
|
||||
self.assert_ver('git', ParseGitVersion())
|
||||
|
||||
|
||||
def _Usage():
|
||||
gitc_usage = ""
|
||||
if get_gitc_manifest_dir():
|
||||
@ -1192,6 +1277,10 @@ def main(orig_args):
|
||||
print("fatal: unable to find repo entry point", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
reqs = Requirements.from_dir(os.path.dirname(repo_main))
|
||||
if reqs:
|
||||
reqs.assert_all()
|
||||
|
||||
ver_str = '.'.join(map(str, VERSION))
|
||||
me = [sys.executable, repo_main,
|
||||
'--repo-dir=%s' % rel_repo_dir,
|
||||
|
57
requirements.json
Normal file
57
requirements.json
Normal file
@ -0,0 +1,57 @@
|
||||
# This file declares various requirements for this version of repo. The
|
||||
# launcher script will load it and check the constraints before trying to run
|
||||
# us. This avoids issues of the launcher using an old version of Python (e.g.
|
||||
# 3.5) while the codebase has moved on to requiring something much newer (e.g.
|
||||
# 3.8). If the launcher tried to import us, it would fail with syntax errors.
|
||||
|
||||
# This is a JSON file with line-level comments allowed.
|
||||
|
||||
# Always keep backwards compatibility in mine. The launcher script is robust
|
||||
# against missing values, but when a field is renamed/removed, it means older
|
||||
# versions of the launcher script won't be able to enforce the constraint.
|
||||
|
||||
# When requiring versions, always use lists as they are easy to parse & compare
|
||||
# in Python. Strings would require futher processing to turn into a list.
|
||||
|
||||
# Version constraints should be expressed in pairs: soft & hard. Soft versions
|
||||
# are when we start warning users that their software too old and we're planning
|
||||
# on dropping support for it, so they need to start planning system upgrades.
|
||||
# Hard versions are when we refuse to work the tool. Users will be shown an
|
||||
# error message before we abort entirely.
|
||||
|
||||
# When deciding whether to upgrade a version requirement, check out the distro
|
||||
# lists to see who will be impacted:
|
||||
# https://gerrit.googlesource.com/git-repo/+/HEAD/docs/release-process.md#Project-References
|
||||
|
||||
{
|
||||
# The repo launcher itself. This allows us to force people to upgrade as some
|
||||
# ignore the warnings about it being out of date, or install ancient versions
|
||||
# to start with for whatever reason.
|
||||
#
|
||||
# NB: Repo launchers started checking this file with repo-2.12, so listing
|
||||
# versions older than that won't make a difference.
|
||||
"repo": {
|
||||
"hard": [2, 11],
|
||||
"soft": [2, 11]
|
||||
},
|
||||
|
||||
# Supported Python versions.
|
||||
#
|
||||
# python-3.6 is in Ubuntu Bionic.
|
||||
# python-3.5 is in Debian Stretch.
|
||||
"python": {
|
||||
"hard": [3, 5],
|
||||
"soft": [3, 6]
|
||||
},
|
||||
|
||||
# Supported git versions.
|
||||
#
|
||||
# git-1.7.2 is in Debian Squeeze.
|
||||
# git-1.7.9 is in Ubuntu Precise.
|
||||
# git-1.9.1 is in Ubuntu Trusty.
|
||||
# git-1.7.10 is in Debian Wheezy.
|
||||
"git": {
|
||||
"hard": [1, 7, 2],
|
||||
"soft": [1, 9, 1]
|
||||
}
|
||||
}
|
@ -51,11 +51,12 @@ import event_log
|
||||
from git_command import GIT, git_require
|
||||
from git_config import GetUrlCookieFile
|
||||
from git_refs import R_HEADS, HEAD
|
||||
import git_superproject
|
||||
import gitc_utils
|
||||
from project import Project
|
||||
from project import RemoteSpec
|
||||
from command import Command, MirrorSafeCommand
|
||||
from error import RepoChangedException, GitError, ManifestParseError
|
||||
from error import BUG_REPORT_URL, RepoChangedException, GitError, ManifestParseError
|
||||
import platform_utils
|
||||
from project import SyncBuffer
|
||||
from progress import Progress
|
||||
@ -241,6 +242,8 @@ later is required to fix a server side protocol bug.
|
||||
p.add_option('--fetch-submodules',
|
||||
dest='fetch_submodules', action='store_true',
|
||||
help='fetch submodules from server')
|
||||
p.add_option('--use-superproject', action='store_true',
|
||||
help='use the manifest superproject to sync projects')
|
||||
p.add_option('--no-tags',
|
||||
dest='tags', default=True, action='store_false',
|
||||
help="don't fetch tags")
|
||||
@ -894,6 +897,41 @@ later is required to fix a server side protocol bug.
|
||||
missing_ok=True,
|
||||
submodules_ok=opt.fetch_submodules)
|
||||
|
||||
if opt.use_superproject:
|
||||
if not self.manifest.superproject:
|
||||
print('error: superproject tag is not defined in manifest.xml',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print('WARNING: --use-superproject is experimental and not '
|
||||
'for general use', file=sys.stderr)
|
||||
superproject_url = self.manifest.superproject['remote'].url
|
||||
if not superproject_url:
|
||||
print('error: superproject URL is not defined in manifest.xml',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
superproject = git_superproject.Superproject(self.manifest.repodir)
|
||||
try:
|
||||
superproject_shas = superproject.GetAllProjectsSHAs(url=superproject_url)
|
||||
except Exception as e:
|
||||
print('error: Cannot get project SHAs for %s: %s: %s' %
|
||||
(superproject_url, type(e).__name__, str(e)),
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
projects_missing_shas = []
|
||||
for project in all_projects:
|
||||
path = project.relpath
|
||||
if not path:
|
||||
continue
|
||||
sha = superproject_shas.get(path)
|
||||
if sha:
|
||||
project.SetRevisionId(sha)
|
||||
else:
|
||||
projects_missing_shas.append(path)
|
||||
if projects_missing_shas:
|
||||
print('error: please file a bug using %s to report missing shas for: %s' %
|
||||
(BUG_REPORT_URL, projects_missing_shas), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
err_network_sync = False
|
||||
err_update_projects = False
|
||||
err_checkout = False
|
||||
|
@ -33,12 +33,14 @@ class Version(Command, MirrorSafeCommand):
|
||||
def Execute(self, opt, args):
|
||||
rp = self.manifest.repoProject
|
||||
rem = rp.GetRemote(rp.remote.name)
|
||||
branch = rp.GetBranch('default')
|
||||
|
||||
# These might not be the same. Report them both.
|
||||
src_ver = RepoSourceVersion()
|
||||
rp_ver = rp.bare_git.describe(HEAD)
|
||||
print('repo version %s' % rp_ver)
|
||||
print(' (from %s)' % rem.url)
|
||||
print(' (tracking %s)' % branch.merge)
|
||||
print(' (%s)' % rp.bare_git.log('-1', '--format=%cD', HEAD))
|
||||
|
||||
if self.wrapper_path is not None:
|
||||
|
82
tests/test_git_superproject.py
Normal file
82
tests/test_git_superproject.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright (C) 2021 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Unittests for the git_superproject.py module."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from error import GitError
|
||||
import git_superproject
|
||||
import platform_utils
|
||||
|
||||
|
||||
class SuperprojectTestCase(unittest.TestCase):
|
||||
"""TestCase for the Superproject module."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up superproject every time."""
|
||||
self.tempdir = tempfile.mkdtemp(prefix='repo_tests')
|
||||
self.repodir = os.path.join(self.tempdir, '.repo')
|
||||
os.mkdir(self.repodir)
|
||||
self._superproject = git_superproject.Superproject(self.repodir)
|
||||
|
||||
def tearDown(self):
|
||||
"""Tear down superproject every time."""
|
||||
platform_utils.rmtree(self.tempdir)
|
||||
|
||||
def test_superproject_get_project_shas_no_url(self):
|
||||
"""Test with no url."""
|
||||
with self.assertRaises(ValueError):
|
||||
self._superproject.GetAllProjectsSHAs(url=None)
|
||||
|
||||
def test_superproject_get_project_shas_invalid_url(self):
|
||||
"""Test with an invalid url."""
|
||||
with self.assertRaises(GitError):
|
||||
self._superproject.GetAllProjectsSHAs(url='localhost')
|
||||
|
||||
def test_superproject_get_project_shas_invalid_branch(self):
|
||||
"""Test with an invalid branch."""
|
||||
with self.assertRaises(GitError):
|
||||
self._superproject.GetAllProjectsSHAs(
|
||||
url='sso://android/platform/superproject',
|
||||
branch='junk')
|
||||
|
||||
def test_superproject_get_project_shas_mock_clone(self):
|
||||
"""Test with _Clone failing."""
|
||||
with self.assertRaises(GitError):
|
||||
with mock.patch.object(self._superproject, '_Clone', return_value=False):
|
||||
self._superproject.GetAllProjectsSHAs(url='localhost')
|
||||
|
||||
def test_superproject_get_project_shas_mock_ls_tree(self):
|
||||
"""Test with LsTree being a mock."""
|
||||
data = ('120000 blob 158258bdf146f159218e2b90f8b699c4d85b5804\tAndroid.bp\x00'
|
||||
'160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
|
||||
'160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00'
|
||||
'120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00'
|
||||
'160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00')
|
||||
with mock.patch.object(self._superproject, '_Clone', return_value=True):
|
||||
with mock.patch.object(self._superproject, '_LsTree', return_value=data):
|
||||
shas = self._superproject.GetAllProjectsSHAs(url='localhost', branch='junk')
|
||||
self.assertEqual(shas, {
|
||||
'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea',
|
||||
'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06',
|
||||
'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928'
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -95,6 +95,8 @@ class EventLogTestCase(unittest.TestCase):
|
||||
# Check for 'version' event specific fields.
|
||||
self.assertIn('evt', version_event)
|
||||
self.assertIn('exe', version_event)
|
||||
# Verify "evt" version field is a string.
|
||||
self.assertIsInstance(version_event['evt'], str)
|
||||
|
||||
def test_start_event(self):
|
||||
"""Test and validate 'start' event data is valid.
|
||||
|
@ -221,6 +221,88 @@ class XmlManifestTests(unittest.TestCase):
|
||||
self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
|
||||
self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
|
||||
|
||||
def test_superproject(self):
|
||||
"""Check superproject settings."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="test-remote" fetch="http://localhost" />
|
||||
<default remote="test-remote" revision="refs/heads/main" />
|
||||
<superproject name="superproject"/>
|
||||
</manifest>
|
||||
""")
|
||||
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
||||
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
|
||||
self.assertEqual(
|
||||
manifest.ToXml().toxml(),
|
||||
'<?xml version="1.0" ?><manifest>' +
|
||||
'<remote name="test-remote" fetch="http://localhost"/>' +
|
||||
'<default remote="test-remote" revision="refs/heads/main"/>' +
|
||||
'<superproject name="superproject"/>' +
|
||||
'</manifest>')
|
||||
|
||||
def test_superproject_with_remote(self):
|
||||
"""Check superproject settings."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="default-remote" fetch="http://localhost" />
|
||||
<remote name="superproject-remote" fetch="http://localhost" />
|
||||
<default remote="default-remote" revision="refs/heads/main" />
|
||||
<superproject name="platform/superproject" remote="superproject-remote"/>
|
||||
</manifest>
|
||||
""")
|
||||
self.assertEqual(manifest.superproject['name'], 'platform/superproject')
|
||||
self.assertEqual(manifest.superproject['remote'].name, 'superproject-remote')
|
||||
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/platform/superproject')
|
||||
self.assertEqual(
|
||||
manifest.ToXml().toxml(),
|
||||
'<?xml version="1.0" ?><manifest>' +
|
||||
'<remote name="default-remote" fetch="http://localhost"/>' +
|
||||
'<remote name="superproject-remote" fetch="http://localhost"/>' +
|
||||
'<default remote="default-remote" revision="refs/heads/main"/>' +
|
||||
'<superproject name="platform/superproject" remote="superproject-remote"/>' +
|
||||
'</manifest>')
|
||||
|
||||
def test_superproject_with_defalut_remote(self):
|
||||
"""Check superproject settings."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="default-remote" fetch="http://localhost" />
|
||||
<default remote="default-remote" revision="refs/heads/main" />
|
||||
<superproject name="superproject" remote="default-remote"/>
|
||||
</manifest>
|
||||
""")
|
||||
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||
self.assertEqual(manifest.superproject['remote'].name, 'default-remote')
|
||||
self.assertEqual(
|
||||
manifest.ToXml().toxml(),
|
||||
'<?xml version="1.0" ?><manifest>' +
|
||||
'<remote name="default-remote" fetch="http://localhost"/>' +
|
||||
'<default remote="default-remote" revision="refs/heads/main"/>' +
|
||||
'<superproject name="superproject"/>' +
|
||||
'</manifest>')
|
||||
|
||||
def test_unknown_tags(self):
|
||||
"""Check superproject settings."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="test-remote" fetch="http://localhost" />
|
||||
<default remote="test-remote" revision="refs/heads/main" />
|
||||
<superproject name="superproject"/>
|
||||
<iankaz value="unknown (possible) future tags are ignored"/>
|
||||
<x-custom-tag>X tags are always ignored</x-custom-tag>
|
||||
</manifest>
|
||||
""")
|
||||
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
||||
self.assertEqual(
|
||||
manifest.ToXml().toxml(),
|
||||
'<?xml version="1.0" ?><manifest>' +
|
||||
'<remote name="test-remote" fetch="http://localhost"/>' +
|
||||
'<default remote="test-remote" revision="refs/heads/main"/>' +
|
||||
'<superproject name="superproject"/>' +
|
||||
'</manifest>')
|
||||
|
||||
def test_project_group(self):
|
||||
"""Check project group settings."""
|
||||
manifest = self.getXmlManifest("""
|
||||
|
@ -19,6 +19,7 @@ from io import StringIO
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
@ -255,6 +256,81 @@ class CheckGitVersion(RepoWrapperTestCase):
|
||||
self.wrapper._CheckGitVersion()
|
||||
|
||||
|
||||
class Requirements(RepoWrapperTestCase):
|
||||
"""Check Requirements handling."""
|
||||
|
||||
def test_missing_file(self):
|
||||
"""Don't crash if the file is missing (old version)."""
|
||||
testdir = os.path.dirname(os.path.realpath(__file__))
|
||||
self.assertIsNone(self.wrapper.Requirements.from_dir(testdir))
|
||||
self.assertIsNone(self.wrapper.Requirements.from_file(
|
||||
os.path.join(testdir, 'xxxxxxxxxxxxxxxxxxxxxxxx')))
|
||||
|
||||
def test_corrupt_data(self):
|
||||
"""If the file can't be parsed, don't blow up."""
|
||||
self.assertIsNone(self.wrapper.Requirements.from_file(__file__))
|
||||
self.assertIsNone(self.wrapper.Requirements.from_data(b'x'))
|
||||
|
||||
def test_valid_data(self):
|
||||
"""Make sure we can parse the file we ship."""
|
||||
self.assertIsNotNone(self.wrapper.Requirements.from_data(b'{}'))
|
||||
rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
self.assertIsNotNone(self.wrapper.Requirements.from_dir(rootdir))
|
||||
self.assertIsNotNone(self.wrapper.Requirements.from_file(os.path.join(
|
||||
rootdir, 'requirements.json')))
|
||||
|
||||
def test_format_ver(self):
|
||||
"""Check format_ver can format."""
|
||||
self.assertEqual('1.2.3', self.wrapper.Requirements._format_ver((1, 2, 3)))
|
||||
self.assertEqual('1', self.wrapper.Requirements._format_ver([1]))
|
||||
|
||||
def test_assert_all_unknown(self):
|
||||
"""Check assert_all works with incompatible file."""
|
||||
reqs = self.wrapper.Requirements({})
|
||||
reqs.assert_all()
|
||||
|
||||
def test_assert_all_new_repo(self):
|
||||
"""Check assert_all accepts new enough repo."""
|
||||
reqs = self.wrapper.Requirements({'repo': {'hard': [1, 0]}})
|
||||
reqs.assert_all()
|
||||
|
||||
def test_assert_all_old_repo(self):
|
||||
"""Check assert_all rejects old repo."""
|
||||
reqs = self.wrapper.Requirements({'repo': {'hard': [99999, 0]}})
|
||||
with self.assertRaises(SystemExit):
|
||||
reqs.assert_all()
|
||||
|
||||
def test_assert_all_new_python(self):
|
||||
"""Check assert_all accepts new enough python."""
|
||||
reqs = self.wrapper.Requirements({'python': {'hard': sys.version_info}})
|
||||
reqs.assert_all()
|
||||
|
||||
def test_assert_all_old_repo(self):
|
||||
"""Check assert_all rejects old repo."""
|
||||
reqs = self.wrapper.Requirements({'python': {'hard': [99999, 0]}})
|
||||
with self.assertRaises(SystemExit):
|
||||
reqs.assert_all()
|
||||
|
||||
def test_assert_ver_unknown(self):
|
||||
"""Check assert_ver works with incompatible file."""
|
||||
reqs = self.wrapper.Requirements({})
|
||||
reqs.assert_ver('xxx', (1, 0))
|
||||
|
||||
def test_assert_ver_new(self):
|
||||
"""Check assert_ver allows new enough versions."""
|
||||
reqs = self.wrapper.Requirements({'git': {'hard': [1, 0], 'soft': [2, 0]}})
|
||||
reqs.assert_ver('git', (1, 0))
|
||||
reqs.assert_ver('git', (1, 5))
|
||||
reqs.assert_ver('git', (2, 0))
|
||||
reqs.assert_ver('git', (2, 5))
|
||||
|
||||
def test_assert_ver_old(self):
|
||||
"""Check assert_ver rejects old versions."""
|
||||
reqs = self.wrapper.Requirements({'git': {'hard': [1, 0], 'soft': [2, 0]}})
|
||||
with self.assertRaises(SystemExit):
|
||||
reqs.assert_ver('git', (0, 5))
|
||||
|
||||
|
||||
class NeedSetupGnuPG(RepoWrapperTestCase):
|
||||
"""Check NeedSetupGnuPG behavior."""
|
||||
|
||||
|
Reference in New Issue
Block a user