mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-30 20:17:08 +00:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
21dce3d8b3 | |||
e3315bb49a | |||
38867fb6d3 | |||
ce64e3d47b | |||
8d43dea6ea | |||
1fd7bc2438 | |||
b5c5a5e068 | |||
0286e31ec7 | |||
ef267722f8 | |||
7caa3658b2 | |||
9e7875315f | |||
db3128f2ec | |||
2a2da80ba6 | |||
6a872c9dae | |||
df6c506a8a | |||
febe73ff16 | |||
e5670c8812 | |||
48b2d10d8f | |||
0588f3dc52 | |||
1bb4fb222d |
@ -142,11 +142,13 @@ User controlled settings are initialized when running `repo init`.
|
||||
| repo.partialclone | `--partial-clone` | Create [partial git clones] |
|
||||
| repo.reference | `--reference` | Reference repo client checkout |
|
||||
| repo.submodules | `--submodules` | Sync git submodules |
|
||||
| repo.superproject | `--use-superproject` | Sync [superproject] |
|
||||
| repo.worktree | `--worktree` | Use `git worktree` for checkouts |
|
||||
| user.email | `--config-name` | User's e-mail address; Copied into `.git/config` when checking out a new project |
|
||||
| user.name | `--config-name` | User's name; Copied into `.git/config` when checking out a new project |
|
||||
|
||||
[partial git clones]: https://git-scm.com/docs/gitrepository-layout#_code_partialclone_code
|
||||
[superproject]: https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
|
||||
|
||||
### Repo hooks settings
|
||||
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -395,7 +395,7 @@ class GitCommand(object):
|
||||
s_in.remove(s)
|
||||
continue
|
||||
if not hasattr(buf, 'encode'):
|
||||
buf = buf.decode()
|
||||
buf = buf.decode('utf-8', 'backslashreplace')
|
||||
if s.std_name == 'stdout':
|
||||
self.stdout += buf
|
||||
else:
|
||||
|
@ -161,6 +161,12 @@ class GitConfig(object):
|
||||
return False
|
||||
return None
|
||||
|
||||
def SetBoolean(self, name, value):
|
||||
"""Set the truthy value for a key."""
|
||||
if value is not None:
|
||||
value = 'true' if value else 'false'
|
||||
self.SetString(name, value)
|
||||
|
||||
def GetString(self, name, all_keys=False):
|
||||
"""Get the first value for a key, or None if it is not defined.
|
||||
|
||||
|
278
git_superproject.py
Normal file
278
git_superproject.py
Normal file
@ -0,0 +1,278 @@
|
||||
# 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 commit ids from Superproject.
|
||||
|
||||
For more information on superproject, check out:
|
||||
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
|
||||
|
||||
Examples:
|
||||
superproject = Superproject()
|
||||
project_commit_ids = superproject.UpdateProjectsRevisionId(projects)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from error import BUG_REPORT_URL
|
||||
from git_command import GitCommand
|
||||
from git_refs import R_HEADS
|
||||
import platform_utils
|
||||
|
||||
_SUPERPROJECT_GIT_NAME = 'superproject.git'
|
||||
_SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml'
|
||||
|
||||
|
||||
class Superproject(object):
|
||||
"""Get commit ids from superproject.
|
||||
|
||||
It does a 'git clone' of superproject and 'git ls-tree' to get list of commit ids
|
||||
for all projects. It contains project_commit_ids which is a dictionary with
|
||||
project/commit id entries.
|
||||
"""
|
||||
def __init__(self, manifest, repodir, superproject_dir='exp-superproject'):
|
||||
"""Initializes superproject.
|
||||
|
||||
Args:
|
||||
manifest: A Manifest object that is to be written to a file.
|
||||
repodir: Path to the .repo/ dir for holding all internal checkout state.
|
||||
It must be in the top directory of the repo client checkout.
|
||||
superproject_dir: Relative path under |repodir| to checkout superproject.
|
||||
"""
|
||||
self._project_commit_ids = None
|
||||
self._manifest = manifest
|
||||
self._branch = self._GetBranch()
|
||||
self._repodir = os.path.abspath(repodir)
|
||||
self._superproject_dir = superproject_dir
|
||||
self._superproject_path = os.path.join(self._repodir, superproject_dir)
|
||||
self._manifest_path = os.path.join(self._superproject_path,
|
||||
_SUPERPROJECT_MANIFEST_NAME)
|
||||
self._work_git = os.path.join(self._superproject_path,
|
||||
_SUPERPROJECT_GIT_NAME)
|
||||
|
||||
@property
|
||||
def project_commit_ids(self):
|
||||
"""Returns a dictionary of projects and their commit ids."""
|
||||
return self._project_commit_ids
|
||||
|
||||
def _GetBranch(self):
|
||||
"""Returns the branch name for getting the approved manifest."""
|
||||
p = self._manifest.manifestProject
|
||||
b = p.GetBranch(p.CurrentBranch)
|
||||
if not b:
|
||||
return None
|
||||
branch = b.merge
|
||||
if branch and branch.startswith(R_HEADS):
|
||||
branch = branch[len(R_HEADS):]
|
||||
return branch
|
||||
|
||||
def _Clone(self, url):
|
||||
"""Do a 'git clone' for the given url.
|
||||
|
||||
Args:
|
||||
url: superproject's url to be passed to git clone.
|
||||
|
||||
Returns:
|
||||
True if git clone is successful, or False.
|
||||
"""
|
||||
if not os.path.exists(self._superproject_path):
|
||||
os.mkdir(self._superproject_path)
|
||||
cmd = ['clone', url, '--filter', 'blob:none', '--bare']
|
||||
if self._branch:
|
||||
cmd += ['--branch', self._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 _Fetch(self):
|
||||
"""Do a 'git fetch' to to fetch the latest content.
|
||||
|
||||
Returns:
|
||||
True if 'git fetch' is successful, or False.
|
||||
"""
|
||||
if not os.path.exists(self._work_git):
|
||||
print('git fetch missing drectory: %s' % self._work_git,
|
||||
file=sys.stderr)
|
||||
return False
|
||||
cmd = ['fetch', 'origin', '+refs/heads/*:refs/heads/*', '--prune']
|
||||
p = GitCommand(None,
|
||||
cmd,
|
||||
cwd=self._work_git,
|
||||
capture_stdout=True,
|
||||
capture_stderr=True)
|
||||
retval = p.Wait()
|
||||
if retval:
|
||||
print('repo: error: git fetch 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 ...'.
|
||||
|
||||
Works only in git repositories.
|
||||
|
||||
Returns:
|
||||
data: data returned from 'git ls-tree ...' instead of None.
|
||||
"""
|
||||
if not os.path.exists(self._work_git):
|
||||
print('git ls-tree missing drectory: %s' % self._work_git,
|
||||
file=sys.stderr)
|
||||
return None
|
||||
data = None
|
||||
branch = 'HEAD' if not self._branch else self._branch
|
||||
cmd = ['ls-tree', '-z', '-r', branch]
|
||||
|
||||
p = GitCommand(None,
|
||||
cmd,
|
||||
cwd=self._work_git,
|
||||
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 Sync(self):
|
||||
"""Sync superproject either by git clone/fetch.
|
||||
|
||||
Returns:
|
||||
True if sync of superproject is successful, or False.
|
||||
"""
|
||||
print('WARNING: --use-superproject is experimental and not '
|
||||
'for general use', file=sys.stderr)
|
||||
|
||||
if not self._manifest.superproject:
|
||||
print('error: superproject tag is not defined in manifest',
|
||||
file=sys.stderr)
|
||||
return False
|
||||
|
||||
url = self._manifest.superproject['remote'].url
|
||||
if not url:
|
||||
print('error: superproject URL is not defined in manifest',
|
||||
file=sys.stderr)
|
||||
return False
|
||||
|
||||
do_clone = True
|
||||
if os.path.exists(self._superproject_path):
|
||||
if not self._Fetch():
|
||||
# If fetch fails due to a corrupted git directory, then do a git clone.
|
||||
platform_utils.rmtree(self._superproject_path)
|
||||
else:
|
||||
do_clone = False
|
||||
if do_clone:
|
||||
if not self._Clone(url):
|
||||
print('error: git clone failed for url: %s' % url, file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _GetAllProjectsCommitIds(self):
|
||||
"""Get commit ids for all projects from superproject and save them in _project_commit_ids.
|
||||
|
||||
Returns:
|
||||
A dictionary with the projects/commit ids on success, otherwise None.
|
||||
"""
|
||||
if not self.Sync():
|
||||
return None
|
||||
|
||||
data = self._LsTree()
|
||||
if not data:
|
||||
print('error: git ls-tree failed for superproject', file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Parse lines like the following to select lines starting with '160000' and
|
||||
# build a dictionary with project path (last element) and its commit id (3rd element).
|
||||
#
|
||||
# 160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00
|
||||
# 120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00
|
||||
commit_ids = {}
|
||||
for line in data.split('\x00'):
|
||||
ls_data = line.split(None, 3)
|
||||
if not ls_data:
|
||||
break
|
||||
if ls_data[0] == '160000':
|
||||
commit_ids[ls_data[3]] = ls_data[2]
|
||||
|
||||
self._project_commit_ids = commit_ids
|
||||
return commit_ids
|
||||
|
||||
def _WriteManfiestFile(self):
|
||||
"""Writes manifest to a file.
|
||||
|
||||
Returns:
|
||||
manifest_path: Path name of the file into which manifest is written instead of None.
|
||||
"""
|
||||
if not os.path.exists(self._superproject_path):
|
||||
print('error: missing superproject directory %s' %
|
||||
self._superproject_path,
|
||||
file=sys.stderr)
|
||||
return None
|
||||
manifest_str = self._manifest.ToXml().toxml()
|
||||
manifest_path = self._manifest_path
|
||||
try:
|
||||
with open(manifest_path, 'w', encoding='utf-8') as fp:
|
||||
fp.write(manifest_str)
|
||||
except IOError as e:
|
||||
print('error: cannot write manifest to %s:\n%s'
|
||||
% (manifest_path, e),
|
||||
file=sys.stderr)
|
||||
return None
|
||||
return manifest_path
|
||||
|
||||
def UpdateProjectsRevisionId(self, projects):
|
||||
"""Update revisionId of every project in projects with the commit id.
|
||||
|
||||
Args:
|
||||
projects: List of projects whose revisionId needs to be updated.
|
||||
|
||||
Returns:
|
||||
manifest_path: Path name of the overriding manfiest file instead of None.
|
||||
"""
|
||||
commit_ids = self._GetAllProjectsCommitIds()
|
||||
if not commit_ids:
|
||||
print('error: Cannot get project commit ids from manifest', file=sys.stderr)
|
||||
return None
|
||||
|
||||
projects_missing_commit_ids = []
|
||||
for project in projects:
|
||||
path = project.relpath
|
||||
if not path:
|
||||
continue
|
||||
commit_id = commit_ids.get(path)
|
||||
if commit_id:
|
||||
project.SetRevisionId(commit_id)
|
||||
else:
|
||||
projects_missing_commit_ids.append(path)
|
||||
if projects_missing_commit_ids:
|
||||
print('error: please file a bug using %s to report missing commit_ids for: %s' %
|
||||
(BUG_REPORT_URL, projects_missing_commit_ids), file=sys.stderr)
|
||||
return None
|
||||
|
||||
manifest_path = self._WriteManfiestFile()
|
||||
return manifest_path
|
@ -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)
|
||||
|
||||
|
@ -403,6 +403,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
revision = self.remotes[p.remote.orig_name].revision or d.revisionExpr
|
||||
if not revision or revision != p.revisionExpr:
|
||||
e.setAttribute('revision', p.revisionExpr)
|
||||
elif p.revisionId:
|
||||
e.setAttribute('revision', p.revisionId)
|
||||
if (p.upstream and (p.upstream != p.revisionExpr or
|
||||
p.upstream != d.upstreamExpr)):
|
||||
e.setAttribute('upstream', p.upstream)
|
||||
@ -463,6 +465,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 +488,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 +560,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 +612,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 +815,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')
|
||||
|
||||
|
12
project.py
12
project.py
@ -438,6 +438,7 @@ class RemoteSpec(object):
|
||||
self.orig_name = orig_name
|
||||
self.fetchUrl = fetchUrl
|
||||
|
||||
|
||||
class Project(object):
|
||||
# These objects can be shared between several working trees.
|
||||
shareable_files = ['description', 'info']
|
||||
@ -1197,6 +1198,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.
|
||||
@ -1924,7 +1928,8 @@ class Project(object):
|
||||
try:
|
||||
# if revision (sha or tag) is not present then following function
|
||||
# throws an error.
|
||||
self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
|
||||
self.bare_git.rev_list('-1', '--missing=allow-any',
|
||||
'%s^0' % self.revisionExpr, '--')
|
||||
return True
|
||||
except GitError:
|
||||
# There is no such persistent revision. We have to fetch it.
|
||||
@ -2467,10 +2472,7 @@ class Project(object):
|
||||
self.config.SetString(key, m.GetString(key))
|
||||
self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
|
||||
self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
|
||||
if self.manifest.IsMirror:
|
||||
self.config.SetString('core.bare', 'true')
|
||||
else:
|
||||
self.config.SetString('core.bare', None)
|
||||
self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
|
||||
except Exception:
|
||||
if init_obj_dir and os.path.exists(self.objdir):
|
||||
platform_utils.rmtree(self.objdir)
|
||||
|
99
repo
99
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
|
||||
@ -323,6 +324,11 @@ def GetParser(gitc_init=False):
|
||||
'each project. See git archive.')
|
||||
group.add_option('--submodules', action='store_true',
|
||||
help='sync any submodules associated with the manifest repo')
|
||||
group.add_option('--use-superproject', action='store_true', default=None,
|
||||
help='use the manifest superproject to sync projects')
|
||||
group.add_option('--no-use-superproject', action='store_false',
|
||||
dest='use_superproject',
|
||||
help='disable use of manifest superprojects')
|
||||
group.add_option('-g', '--groups', default='default',
|
||||
help='restrict manifest projects to ones with specified '
|
||||
'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
|
||||
@ -332,7 +338,8 @@ def GetParser(gitc_init=False):
|
||||
'platform group [auto|all|none|linux|darwin|...]',
|
||||
metavar='PLATFORM')
|
||||
group.add_option('--clone-bundle', action='store_true',
|
||||
help='enable use of /clone.bundle on HTTP/HTTPS (default if not --partial-clone)')
|
||||
help='enable use of /clone.bundle on HTTP/HTTPS '
|
||||
'(default if not --partial-clone)')
|
||||
group.add_option('--no-clone-bundle',
|
||||
dest='clone_bundle', action='store_false',
|
||||
help='disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone)')
|
||||
@ -1035,6 +1042,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 +1283,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]
|
||||
}
|
||||
}
|
@ -191,12 +191,12 @@ synced and their revisions won't be found.
|
||||
else:
|
||||
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
|
||||
|
||||
manifest1 = RepoClient(self.manifest.repodir)
|
||||
manifest1 = RepoClient(self.repodir)
|
||||
manifest1.Override(args[0], load_local_manifests=False)
|
||||
if len(args) == 1:
|
||||
manifest2 = self.manifest
|
||||
else:
|
||||
manifest2 = RepoClient(self.manifest.repodir)
|
||||
manifest2 = RepoClient(self.repodir)
|
||||
manifest2.Override(args[1], load_local_manifests=False)
|
||||
|
||||
diff = manifest1.projectsDiff(manifest2)
|
||||
|
@ -25,6 +25,7 @@ from error import ManifestParseError
|
||||
from project import SyncBuffer
|
||||
from git_config import GitConfig
|
||||
from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD
|
||||
import git_superproject
|
||||
import platform_utils
|
||||
from wrapper import Wrapper
|
||||
|
||||
@ -134,6 +135,11 @@ to update the working directory files.
|
||||
g.add_option('--submodules',
|
||||
dest='submodules', action='store_true',
|
||||
help='sync any submodules associated with the manifest repo')
|
||||
g.add_option('--use-superproject', action='store_true',
|
||||
help='use the manifest superproject to sync projects')
|
||||
g.add_option('--no-use-superproject', action='store_false',
|
||||
dest='use_superproject',
|
||||
help='disable use of manifest superprojects')
|
||||
g.add_option('-g', '--groups',
|
||||
dest='groups', default='default',
|
||||
help='restrict manifest projects to ones with specified '
|
||||
@ -176,6 +182,14 @@ to update the working directory files.
|
||||
return {'REPO_MANIFEST_URL': 'manifest_url',
|
||||
'REPO_MIRROR_LOCATION': 'reference'}
|
||||
|
||||
def _CloneSuperproject(self):
|
||||
"""Clone the superproject based on the superproject's url and branch."""
|
||||
superproject = git_superproject.Superproject(self.manifest,
|
||||
self.repodir)
|
||||
if not superproject.Sync():
|
||||
print('error: git update of superproject failed', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _SyncManifest(self, opt):
|
||||
m = self.manifest.manifestProject
|
||||
is_new = not m.Exists
|
||||
@ -250,7 +264,7 @@ to update the working directory files.
|
||||
m.config.SetString('repo.reference', opt.reference)
|
||||
|
||||
if opt.dissociate:
|
||||
m.config.SetString('repo.dissociate', 'true')
|
||||
m.config.SetBoolean('repo.dissociate', opt.dissociate)
|
||||
|
||||
if opt.worktree:
|
||||
if opt.mirror:
|
||||
@ -261,14 +275,14 @@ to update the working directory files.
|
||||
print('fatal: --submodules and --worktree are incompatible',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
m.config.SetString('repo.worktree', 'true')
|
||||
m.config.SetBoolean('repo.worktree', opt.worktree)
|
||||
if is_new:
|
||||
m.use_git_worktrees = True
|
||||
print('warning: --worktree is experimental!', file=sys.stderr)
|
||||
|
||||
if opt.archive:
|
||||
if is_new:
|
||||
m.config.SetString('repo.archive', 'true')
|
||||
m.config.SetBoolean('repo.archive', opt.archive)
|
||||
else:
|
||||
print('fatal: --archive is only supported when initializing a new '
|
||||
'workspace.', file=sys.stderr)
|
||||
@ -278,7 +292,7 @@ to update the working directory files.
|
||||
|
||||
if opt.mirror:
|
||||
if is_new:
|
||||
m.config.SetString('repo.mirror', 'true')
|
||||
m.config.SetBoolean('repo.mirror', opt.mirror)
|
||||
else:
|
||||
print('fatal: --mirror is only supported when initializing a new '
|
||||
'workspace.', file=sys.stderr)
|
||||
@ -291,7 +305,7 @@ to update the working directory files.
|
||||
print('fatal: --mirror and --partial-clone are mutually exclusive',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
m.config.SetString('repo.partialclone', 'true')
|
||||
m.config.SetBoolean('repo.partialclone', opt.partial_clone)
|
||||
if opt.clone_filter:
|
||||
m.config.SetString('repo.clonefilter', opt.clone_filter)
|
||||
else:
|
||||
@ -300,10 +314,13 @@ to update the working directory files.
|
||||
if opt.clone_bundle is None:
|
||||
opt.clone_bundle = False if opt.partial_clone else True
|
||||
else:
|
||||
m.config.SetString('repo.clonebundle', 'true' if opt.clone_bundle else 'false')
|
||||
m.config.SetBoolean('repo.clonebundle', opt.clone_bundle)
|
||||
|
||||
if opt.submodules:
|
||||
m.config.SetString('repo.submodules', 'true')
|
||||
m.config.SetBoolean('repo.submodules', opt.submodules)
|
||||
|
||||
if opt.use_superproject is not None:
|
||||
m.config.SetBoolean('repo.superproject', opt.use_superproject)
|
||||
|
||||
if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet, verbose=opt.verbose,
|
||||
clone_bundle=opt.clone_bundle,
|
||||
@ -519,6 +536,9 @@ to update the working directory files.
|
||||
self._SyncManifest(opt)
|
||||
self._LinkManifest(opt.manifest_name)
|
||||
|
||||
if self.manifest.manifestProject.config.GetBoolean('repo.superproject'):
|
||||
self._CloneSuperproject()
|
||||
|
||||
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
|
||||
if opt.config_name or self._ShouldConfigureUser(opt):
|
||||
self._ConfigureUser(opt)
|
||||
|
@ -51,6 +51,7 @@ 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
|
||||
@ -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")
|
||||
@ -268,6 +271,42 @@ later is required to fix a server side protocol bug.
|
||||
dest='repo_upgraded', action='store_true',
|
||||
help=SUPPRESS_HELP)
|
||||
|
||||
def _GetBranch(self):
|
||||
"""Returns the branch name for getting the approved manifest."""
|
||||
p = self.manifest.manifestProject
|
||||
b = p.GetBranch(p.CurrentBranch)
|
||||
branch = b.merge
|
||||
if branch.startswith(R_HEADS):
|
||||
branch = branch[len(R_HEADS):]
|
||||
return branch
|
||||
|
||||
def _UpdateProjectsRevisionId(self, opt, args):
|
||||
"""Update revisionId of every project with the SHA from superproject.
|
||||
|
||||
This function updates each project's revisionId with SHA from superproject.
|
||||
It writes the updated manifest into a file and reloads the manifest from it.
|
||||
|
||||
Args:
|
||||
opt: Program options returned from optparse. See _Options().
|
||||
args: Arguments to pass to GetProjects. See the GetProjects
|
||||
docstring for details.
|
||||
|
||||
Returns:
|
||||
Returns path to the overriding manifest file.
|
||||
"""
|
||||
superproject = git_superproject.Superproject(self.manifest,
|
||||
self.repodir)
|
||||
all_projects = self.GetProjects(args,
|
||||
missing_ok=True,
|
||||
submodules_ok=opt.fetch_submodules)
|
||||
manifest_path = superproject.UpdateProjectsRevisionId(all_projects)
|
||||
if not manifest_path:
|
||||
print('error: Update of revsionId from superproject has failed',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
self._ReloadManifest(manifest_path)
|
||||
return manifest_path
|
||||
|
||||
def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
|
||||
"""Main function of the fetch threads.
|
||||
|
||||
@ -558,8 +597,9 @@ later is required to fix a server side protocol bug.
|
||||
# Make sure pruning never kicks in with shared projects.
|
||||
if (not project.use_git_worktrees and
|
||||
len(project.manifest.GetProjectsWithName(project.name)) > 1):
|
||||
print('%s: Shared project %s found, disabling pruning.' %
|
||||
(project.relpath, project.name))
|
||||
if not opt.quiet:
|
||||
print('%s: Shared project %s found, disabling pruning.' %
|
||||
(project.relpath, project.name))
|
||||
if git_require((2, 7, 0)):
|
||||
project.EnableRepositoryExtension('preciousObjects')
|
||||
else:
|
||||
@ -624,7 +664,7 @@ later is required to fix a server side protocol bug.
|
||||
if project.relpath:
|
||||
new_project_paths.append(project.relpath)
|
||||
file_name = 'project.list'
|
||||
file_path = os.path.join(self.manifest.repodir, file_name)
|
||||
file_path = os.path.join(self.repodir, file_name)
|
||||
old_project_paths = []
|
||||
|
||||
if os.path.exists(file_path):
|
||||
@ -708,11 +748,7 @@ later is required to fix a server side protocol bug.
|
||||
try:
|
||||
server = xmlrpc.client.Server(manifest_server, transport=transport)
|
||||
if opt.smart_sync:
|
||||
p = self.manifest.manifestProject
|
||||
b = p.GetBranch(p.CurrentBranch)
|
||||
branch = b.merge
|
||||
if branch.startswith(R_HEADS):
|
||||
branch = branch[len(R_HEADS):]
|
||||
branch = self._GetBranch()
|
||||
|
||||
if 'SYNC_TARGET' in os.environ:
|
||||
target = os.environ['SYNC_TARGET']
|
||||
@ -855,6 +891,9 @@ later is required to fix a server side protocol bug.
|
||||
else:
|
||||
self._UpdateManifestProject(opt, mp, manifest_name)
|
||||
|
||||
if opt.use_superproject:
|
||||
manifest_name = self._UpdateProjectsRevisionId(opt, args)
|
||||
|
||||
if self.gitc_manifest:
|
||||
gitc_manifest_projects = self.GetProjects(args,
|
||||
missing_ok=True)
|
||||
|
@ -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:
|
||||
|
@ -15,6 +15,7 @@
|
||||
"""Unittests for the git_config.py module."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import git_config
|
||||
@ -26,9 +27,8 @@ def fixture(*paths):
|
||||
return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
|
||||
|
||||
|
||||
class GitConfigUnitTest(unittest.TestCase):
|
||||
"""Tests the GitConfig class.
|
||||
"""
|
||||
class GitConfigReadOnlyTests(unittest.TestCase):
|
||||
"""Read-only tests of the GitConfig class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Create a GitConfig object using the test.gitconfig fixture.
|
||||
@ -105,5 +105,69 @@ class GitConfigUnitTest(unittest.TestCase):
|
||||
self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
|
||||
|
||||
|
||||
class GitConfigReadWriteTests(unittest.TestCase):
|
||||
"""Read/write tests of the GitConfig class."""
|
||||
|
||||
def setUp(self):
|
||||
self.tmpfile = tempfile.NamedTemporaryFile()
|
||||
self.config = self.get_config()
|
||||
|
||||
def get_config(self):
|
||||
"""Get a new GitConfig instance."""
|
||||
return git_config.GitConfig(self.tmpfile.name)
|
||||
|
||||
def test_SetString(self):
|
||||
"""Test SetString behavior."""
|
||||
# Set a value.
|
||||
self.assertIsNone(self.config.GetString('foo.bar'))
|
||||
self.config.SetString('foo.bar', 'val')
|
||||
self.assertEqual('val', self.config.GetString('foo.bar'))
|
||||
|
||||
# Make sure the value was actually written out.
|
||||
config = self.get_config()
|
||||
self.assertEqual('val', config.GetString('foo.bar'))
|
||||
|
||||
# Update the value.
|
||||
self.config.SetString('foo.bar', 'valll')
|
||||
self.assertEqual('valll', self.config.GetString('foo.bar'))
|
||||
config = self.get_config()
|
||||
self.assertEqual('valll', config.GetString('foo.bar'))
|
||||
|
||||
# Delete the value.
|
||||
self.config.SetString('foo.bar', None)
|
||||
self.assertIsNone(self.config.GetString('foo.bar'))
|
||||
config = self.get_config()
|
||||
self.assertIsNone(config.GetString('foo.bar'))
|
||||
|
||||
def test_SetBoolean(self):
|
||||
"""Test SetBoolean behavior."""
|
||||
# Set a true value.
|
||||
self.assertIsNone(self.config.GetBoolean('foo.bar'))
|
||||
for val in (True, 1):
|
||||
self.config.SetBoolean('foo.bar', val)
|
||||
self.assertTrue(self.config.GetBoolean('foo.bar'))
|
||||
|
||||
# Make sure the value was actually written out.
|
||||
config = self.get_config()
|
||||
self.assertTrue(config.GetBoolean('foo.bar'))
|
||||
self.assertEqual('true', config.GetString('foo.bar'))
|
||||
|
||||
# Set a false value.
|
||||
for val in (False, 0):
|
||||
self.config.SetBoolean('foo.bar', val)
|
||||
self.assertFalse(self.config.GetBoolean('foo.bar'))
|
||||
|
||||
# Make sure the value was actually written out.
|
||||
config = self.get_config()
|
||||
self.assertFalse(config.GetBoolean('foo.bar'))
|
||||
self.assertEqual('false', config.GetString('foo.bar'))
|
||||
|
||||
# Delete the value.
|
||||
self.config.SetBoolean('foo.bar', None)
|
||||
self.assertIsNone(self.config.GetBoolean('foo.bar'))
|
||||
config = self.get_config()
|
||||
self.assertIsNone(config.GetBoolean('foo.bar'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
177
tests/test_git_superproject.py
Normal file
177
tests/test_git_superproject.py
Normal file
@ -0,0 +1,177 @@
|
||||
# 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
|
||||
|
||||
import git_superproject
|
||||
import manifest_xml
|
||||
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')
|
||||
self.manifest_file = os.path.join(
|
||||
self.repodir, manifest_xml.MANIFEST_FILE_NAME)
|
||||
os.mkdir(self.repodir)
|
||||
|
||||
# The manifest parsing really wants a git repo currently.
|
||||
gitdir = os.path.join(self.repodir, 'manifests.git')
|
||||
os.mkdir(gitdir)
|
||||
with open(os.path.join(gitdir, 'config'), 'w') as fp:
|
||||
fp.write("""[remote "origin"]
|
||||
url = https://localhost:0/manifest
|
||||
""")
|
||||
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="default-remote" fetch="http://localhost" />
|
||||
<default remote="default-remote" revision="refs/heads/main" />
|
||||
<superproject name="superproject"/>
|
||||
<project path="art" name="platform/art" />
|
||||
</manifest>
|
||||
""")
|
||||
self._superproject = git_superproject.Superproject(manifest, self.repodir)
|
||||
|
||||
def tearDown(self):
|
||||
"""Tear down superproject every time."""
|
||||
platform_utils.rmtree(self.tempdir)
|
||||
|
||||
def getXmlManifest(self, data):
|
||||
"""Helper to initialize a manifest for testing."""
|
||||
with open(self.manifest_file, 'w') as fp:
|
||||
fp.write(data)
|
||||
return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
|
||||
|
||||
def test_superproject_get_superproject_no_superproject(self):
|
||||
"""Test with no url."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
</manifest>
|
||||
""")
|
||||
superproject = git_superproject.Superproject(manifest, self.repodir)
|
||||
self.assertFalse(superproject.Sync())
|
||||
|
||||
def test_superproject_get_superproject_invalid_url(self):
|
||||
"""Test with an invalid url."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="test-remote" fetch="localhost" />
|
||||
<default remote="test-remote" revision="refs/heads/main" />
|
||||
<superproject name="superproject"/>
|
||||
</manifest>
|
||||
""")
|
||||
superproject = git_superproject.Superproject(manifest, self.repodir)
|
||||
self.assertFalse(superproject.Sync())
|
||||
|
||||
def test_superproject_get_superproject_invalid_branch(self):
|
||||
"""Test with an invalid branch."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="test-remote" fetch="localhost" />
|
||||
<default remote="test-remote" revision="refs/heads/main" />
|
||||
<superproject name="superproject"/>
|
||||
</manifest>
|
||||
""")
|
||||
superproject = git_superproject.Superproject(manifest, self.repodir)
|
||||
with mock.patch.object(self._superproject, '_GetBranch', return_value='junk'):
|
||||
self.assertFalse(superproject.Sync())
|
||||
|
||||
def test_superproject_get_superproject_mock_clone(self):
|
||||
"""Test with _Clone failing."""
|
||||
with mock.patch.object(self._superproject, '_Clone', return_value=False):
|
||||
self.assertFalse(self._superproject.Sync())
|
||||
|
||||
def test_superproject_get_superproject_mock_fetch(self):
|
||||
"""Test with _Fetch failing and _clone being called."""
|
||||
with mock.patch.object(self._superproject, '_Clone', return_value=True):
|
||||
os.mkdir(self._superproject._superproject_path)
|
||||
with mock.patch.object(self._superproject, '_Fetch', return_value=False):
|
||||
self.assertTrue(self._superproject.Sync())
|
||||
|
||||
def test_superproject_get_all_project_commit_ids_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):
|
||||
commit_ids = self._superproject._GetAllProjectsCommitIds()
|
||||
self.assertEqual(commit_ids, {
|
||||
'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea',
|
||||
'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06',
|
||||
'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928'
|
||||
})
|
||||
|
||||
def test_superproject_write_manifest_file(self):
|
||||
"""Test with writing manifest to a file after setting revisionId."""
|
||||
self.assertEqual(len(self._superproject._manifest.projects), 1)
|
||||
project = self._superproject._manifest.projects[0]
|
||||
project.SetRevisionId('ABCDEF')
|
||||
# Create temporary directory so that it can write the file.
|
||||
os.mkdir(self._superproject._superproject_path)
|
||||
manifest_path = self._superproject._WriteManfiestFile()
|
||||
self.assertIsNotNone(manifest_path)
|
||||
with open(manifest_path, 'r') as fp:
|
||||
manifest_xml = fp.read()
|
||||
self.assertEqual(
|
||||
manifest_xml,
|
||||
'<?xml version="1.0" ?><manifest>' +
|
||||
'<remote name="default-remote" fetch="http://localhost"/>' +
|
||||
'<default remote="default-remote" revision="refs/heads/main"/>' +
|
||||
'<project name="platform/art" path="art" revision="ABCDEF"/>' +
|
||||
'<superproject name="superproject"/>' +
|
||||
'</manifest>')
|
||||
|
||||
def test_superproject_update_project_revision_id(self):
|
||||
"""Test with LsTree being a mock."""
|
||||
self.assertEqual(len(self._superproject._manifest.projects), 1)
|
||||
projects = self._superproject._manifest.projects
|
||||
data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
|
||||
'160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00')
|
||||
with mock.patch.object(self._superproject, '_Clone', return_value=True):
|
||||
with mock.patch.object(self._superproject, '_Fetch', return_value=True):
|
||||
with mock.patch.object(self._superproject,
|
||||
'_LsTree',
|
||||
return_value=data):
|
||||
# Create temporary directory so that it can write the file.
|
||||
os.mkdir(self._superproject._superproject_path)
|
||||
manifest_path = self._superproject.UpdateProjectsRevisionId(projects)
|
||||
self.assertIsNotNone(manifest_path)
|
||||
with open(manifest_path, 'r') as fp:
|
||||
manifest_xml = fp.read()
|
||||
self.assertEqual(
|
||||
manifest_xml,
|
||||
'<?xml version="1.0" ?><manifest>' +
|
||||
'<remote name="default-remote" fetch="http://localhost"/>' +
|
||||
'<default remote="default-remote" revision="refs/heads/main"/>' +
|
||||
'<project name="platform/art" path="art" ' +
|
||||
'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea"/>' +
|
||||
'<superproject name="superproject"/>' +
|
||||
'</manifest>')
|
||||
|
||||
|
||||
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("""
|
||||
@ -245,6 +327,26 @@ class XmlManifestTests(unittest.TestCase):
|
||||
result['extras'],
|
||||
['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
|
||||
|
||||
def test_project_set_revision_id(self):
|
||||
"""Check setting of project's revisionId."""
|
||||
manifest = self.getXmlManifest("""
|
||||
<manifest>
|
||||
<remote name="default-remote" fetch="http://localhost" />
|
||||
<default remote="default-remote" revision="refs/heads/main" />
|
||||
<project name="test-name"/>
|
||||
</manifest>
|
||||
""")
|
||||
self.assertEqual(len(manifest.projects), 1)
|
||||
project = manifest.projects[0]
|
||||
project.SetRevisionId('ABCDEF')
|
||||
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"/>' +
|
||||
'<project name="test-name" revision="ABCDEF"/>' +
|
||||
'</manifest>')
|
||||
|
||||
def test_include_levels(self):
|
||||
root_m = os.path.join(self.manifest_dir, 'root.xml')
|
||||
with open(root_m, 'w') as fp:
|
||||
|
@ -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