From ea2e330e43c182dc16b0111ebc69ee5a71ee4ce1 Mon Sep 17 00:00:00 2001 From: Gavin Mak Date: Sat, 11 Mar 2023 06:46:20 +0000 Subject: Format codebase with black and check formatting in CQ Apply rules set by https://gerrit-review.googlesource.com/c/git-repo/+/362954/ across the codebase and fix any lingering errors caught by flake8. Also check black formatting in run_tests (and CQ). Bug: b/267675342 Change-Id: I972d77649dac351150dcfeb1cd1ad0ea2efc1956 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/363474 Reviewed-by: Mike Frysinger Tested-by: Gavin Mak Commit-Queue: Gavin Mak --- git_superproject.py | 880 +++++++++++++++++++++++++++++----------------------- 1 file changed, 485 insertions(+), 395 deletions(-) (limited to 'git_superproject.py') diff --git a/git_superproject.py b/git_superproject.py index 69a4d1fe..f1b4f231 100644 --- a/git_superproject.py +++ b/git_superproject.py @@ -12,7 +12,7 @@ # 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. +"""Provide functionality to get projects and their commit ids from Superproject. For more information on superproject, check out: https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects @@ -33,434 +33,524 @@ from git_command import git_require, GitCommand from git_config import RepoConfig from git_refs import GitRefs -_SUPERPROJECT_GIT_NAME = 'superproject.git' -_SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml' +_SUPERPROJECT_GIT_NAME = "superproject.git" +_SUPERPROJECT_MANIFEST_NAME = "superproject_override.xml" class SyncResult(NamedTuple): - """Return the status of sync and whether caller should exit.""" + """Return the status of sync and whether caller should exit.""" - # Whether the superproject sync was successful. - success: bool - # Whether the caller should exit. - fatal: bool + # Whether the superproject sync was successful. + success: bool + # Whether the caller should exit. + fatal: bool class CommitIdsResult(NamedTuple): - """Return the commit ids and whether caller should exit.""" + """Return the commit ids and whether caller should exit.""" - # A dictionary with the projects/commit ids on success, otherwise None. - commit_ids: dict - # Whether the caller should exit. - fatal: bool + # A dictionary with the projects/commit ids on success, otherwise None. + commit_ids: dict + # Whether the caller should exit. + fatal: bool class UpdateProjectsResult(NamedTuple): - """Return the overriding manifest file and whether caller should exit.""" + """Return the overriding manifest file and whether caller should exit.""" - # Path name of the overriding manifest file if successful, otherwise None. - manifest_path: str - # Whether the caller should exit. - fatal: bool + # Path name of the overriding manifest file if successful, otherwise None. + manifest_path: str + # Whether the caller should exit. + fatal: bool class Superproject(object): - """Get commit ids from superproject. + """Get commit ids from superproject. - Initializes a local copy of a superproject for the manifest. This allows - lookup of commit ids for all projects. It contains _project_commit_ids which - is a dictionary with project/commit id entries. - """ - def __init__(self, manifest, name, remote, revision, - superproject_dir='exp-superproject'): - """Initializes superproject. - - Args: - manifest: A Manifest object that is to be written to a file. - name: The unique name of the superproject - remote: The RemoteSpec for the remote. - revision: The name of the git branch to track. - superproject_dir: Relative path under |manifest.subdir| to checkout - superproject. - """ - self._project_commit_ids = None - self._manifest = manifest - self.name = name - self.remote = remote - self.revision = self._branch = revision - self._repodir = manifest.repodir - self._superproject_dir = superproject_dir - self._superproject_path = manifest.SubmanifestInfoDir(manifest.path_prefix, - superproject_dir) - self._manifest_path = os.path.join(self._superproject_path, - _SUPERPROJECT_MANIFEST_NAME) - git_name = hashlib.md5(remote.name.encode('utf8')).hexdigest() + '-' - self._remote_url = remote.url - self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME - self._work_git = os.path.join(self._superproject_path, self._work_git_name) - - # The following are command arguemnts, rather than superproject attributes, - # and were included here originally. They should eventually become - # arguments that are passed down from the public methods, instead of being - # treated as attributes. - self._git_event_log = None - self._quiet = False - self._print_messages = False - - def SetQuiet(self, value): - """Set the _quiet attribute.""" - self._quiet = value - - def SetPrintMessages(self, value): - """Set the _print_messages attribute.""" - self._print_messages = value - - @property - def project_commit_ids(self): - """Returns a dictionary of projects and their commit ids.""" - return self._project_commit_ids - - @property - def manifest_path(self): - """Returns the manifest path if the path exists or None.""" - return self._manifest_path if os.path.exists(self._manifest_path) else None - - def _LogMessage(self, fmt, *inputs): - """Logs message to stderr and _git_event_log.""" - message = f'{self._LogMessagePrefix()} {fmt.format(*inputs)}' - if self._print_messages: - print(message, file=sys.stderr) - self._git_event_log.ErrorEvent(message, fmt) - - def _LogMessagePrefix(self): - """Returns the prefix string to be logged in each log message""" - return f'repo superproject branch: {self._branch} url: {self._remote_url}' - - def _LogError(self, fmt, *inputs): - """Logs error message to stderr and _git_event_log.""" - self._LogMessage(f'error: {fmt}', *inputs) - - def _LogWarning(self, fmt, *inputs): - """Logs warning message to stderr and _git_event_log.""" - self._LogMessage(f'warning: {fmt}', *inputs) - - def _Init(self): - """Sets up a local Git repository to get a copy of a superproject. - - Returns: - True if initialization is successful, or False. - """ - if not os.path.exists(self._superproject_path): - os.mkdir(self._superproject_path) - if not self._quiet and not os.path.exists(self._work_git): - print('%s: Performing initial setup for superproject; this might take ' - 'several minutes.' % self._work_git) - cmd = ['init', '--bare', self._work_git_name] - p = GitCommand(None, - cmd, - cwd=self._superproject_path, - capture_stdout=True, - capture_stderr=True) - retval = p.Wait() - if retval: - self._LogWarning('git init call failed, command: git {}, ' - 'return code: {}, stderr: {}', cmd, retval, p.stderr) - return False - return True - - def _Fetch(self): - """Fetches a local copy of a superproject for the manifest based on |_remote_url|. - - Returns: - True if fetch is successful, or False. - """ - if not os.path.exists(self._work_git): - self._LogWarning('git fetch missing directory: {}', self._work_git) - return False - if not git_require((2, 28, 0)): - self._LogWarning('superproject requires a git version 2.28 or later') - return False - cmd = ['fetch', self._remote_url, '--depth', '1', '--force', '--no-tags', - '--filter', 'blob:none'] - - # Check if there is a local ref that we can pass to --negotiation-tip. - # If this is the first fetch, it does not exist yet. - # We use --negotiation-tip to speed up the fetch. Superproject branches do - # not share commits. So this lets git know it only needs to send commits - # reachable from the specified local refs. - rev_commit = GitRefs(self._work_git).get(f'refs/heads/{self.revision}') - if rev_commit: - cmd.extend(['--negotiation-tip', rev_commit]) - - if self._branch: - cmd += [self._branch + ':' + self._branch] - p = GitCommand(None, - cmd, - cwd=self._work_git, - capture_stdout=True, - capture_stderr=True) - retval = p.Wait() - if retval: - self._LogWarning('git fetch call failed, command: git {}, ' - 'return code: {}, stderr: {}', cmd, retval, p.stderr) - return False - return True - - def _LsTree(self): - """Gets the commit ids for all projects. - - Works only in git repositories. - - Returns: - data: data returned from 'git ls-tree ...' instead of None. - """ - if not os.path.exists(self._work_git): - self._LogWarning('git ls-tree missing directory: {}', self._work_git) - 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: - self._LogWarning('git ls-tree call failed, command: git {}, ' - 'return code: {}, stderr: {}', cmd, retval, p.stderr) - return data - - def Sync(self, git_event_log): - """Gets a local copy of a superproject for the manifest. - - Args: - git_event_log: an EventLog, for git tracing. - - Returns: - SyncResult - """ - self._git_event_log = git_event_log - if not self._manifest.superproject: - self._LogWarning('superproject tag is not defined in manifest: {}', - self._manifest.manifestFile) - return SyncResult(False, False) - - _PrintBetaNotice() - - should_exit = True - if not self._remote_url: - self._LogWarning('superproject URL is not defined in manifest: {}', - self._manifest.manifestFile) - return SyncResult(False, should_exit) - - if not self._Init(): - return SyncResult(False, should_exit) - if not self._Fetch(): - return SyncResult(False, should_exit) - if not self._quiet: - print('%s: Initial setup for superproject completed.' % self._work_git) - return SyncResult(True, False) - - def _GetAllProjectsCommitIds(self): - """Get commit ids for all projects from superproject and save them in _project_commit_ids. - - Returns: - CommitIdsResult - """ - sync_result = self.Sync(self._git_event_log) - if not sync_result.success: - return CommitIdsResult(None, sync_result.fatal) - - data = self._LsTree() - if not data: - self._LogWarning('git ls-tree failed to return data for manifest: {}', - self._manifest.manifestFile) - return CommitIdsResult(None, True) - - # 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 CommitIdsResult(commit_ids, False) - - def _WriteManifestFile(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): - self._LogWarning('missing superproject directory: {}', self._superproject_path) - return None - manifest_str = self._manifest.ToXml(groups=self._manifest.GetGroupsStr(), - omit_local=True).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: - self._LogError('cannot write manifest to : {} {}', - manifest_path, e) - return None - return manifest_path - - def _SkipUpdatingProjectRevisionId(self, project): - """Checks if a project's revision id needs to be updated or not. - - Revision id for projects from local manifest will not be updated. - - Args: - project: project whose revision id is being updated. - - Returns: - True if a project's revision id should not be updated, or False, + Initializes a local copy of a superproject for the manifest. This allows + lookup of commit ids for all projects. It contains _project_commit_ids which + is a dictionary with project/commit id entries. """ - path = project.relpath - if not path: - return True - # Skip the project with revisionId. - if project.revisionId: - return True - # Skip the project if it comes from the local manifest. - return project.manifest.IsFromLocalManifest(project) - - def UpdateProjectsRevisionId(self, projects, git_event_log): - """Update revisionId of every project in projects with the commit id. - - Args: - projects: a list of projects whose revisionId needs to be updated. - git_event_log: an EventLog, for git tracing. - Returns: - UpdateProjectsResult - """ - self._git_event_log = git_event_log - commit_ids_result = self._GetAllProjectsCommitIds() - commit_ids = commit_ids_result.commit_ids - if not commit_ids: - return UpdateProjectsResult(None, commit_ids_result.fatal) - - projects_missing_commit_ids = [] - for project in projects: - if self._SkipUpdatingProjectRevisionId(project): - continue - path = project.relpath - commit_id = commit_ids.get(path) - if not commit_id: - projects_missing_commit_ids.append(path) - - # If superproject doesn't have a commit id for a project, then report an - # error event and continue as if do not use superproject is specified. - if projects_missing_commit_ids: - self._LogWarning('please file a bug using {} to report missing ' - 'commit_ids for: {}', self._manifest.contactinfo.bugurl, - projects_missing_commit_ids) - return UpdateProjectsResult(None, False) - - for project in projects: - if not self._SkipUpdatingProjectRevisionId(project): - project.SetRevisionId(commit_ids.get(project.relpath)) - - manifest_path = self._WriteManifestFile() - return UpdateProjectsResult(manifest_path, False) + def __init__( + self, + manifest, + name, + remote, + revision, + superproject_dir="exp-superproject", + ): + """Initializes superproject. + + Args: + manifest: A Manifest object that is to be written to a file. + name: The unique name of the superproject + remote: The RemoteSpec for the remote. + revision: The name of the git branch to track. + superproject_dir: Relative path under |manifest.subdir| to checkout + superproject. + """ + self._project_commit_ids = None + self._manifest = manifest + self.name = name + self.remote = remote + self.revision = self._branch = revision + self._repodir = manifest.repodir + self._superproject_dir = superproject_dir + self._superproject_path = manifest.SubmanifestInfoDir( + manifest.path_prefix, superproject_dir + ) + self._manifest_path = os.path.join( + self._superproject_path, _SUPERPROJECT_MANIFEST_NAME + ) + git_name = hashlib.md5(remote.name.encode("utf8")).hexdigest() + "-" + self._remote_url = remote.url + self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME + self._work_git = os.path.join( + self._superproject_path, self._work_git_name + ) + + # The following are command arguemnts, rather than superproject + # attributes, and were included here originally. They should eventually + # become arguments that are passed down from the public methods, instead + # of being treated as attributes. + self._git_event_log = None + self._quiet = False + self._print_messages = False + + def SetQuiet(self, value): + """Set the _quiet attribute.""" + self._quiet = value + + def SetPrintMessages(self, value): + """Set the _print_messages attribute.""" + self._print_messages = value + + @property + def project_commit_ids(self): + """Returns a dictionary of projects and their commit ids.""" + return self._project_commit_ids + + @property + def manifest_path(self): + """Returns the manifest path if the path exists or None.""" + return ( + self._manifest_path if os.path.exists(self._manifest_path) else None + ) + + def _LogMessage(self, fmt, *inputs): + """Logs message to stderr and _git_event_log.""" + message = f"{self._LogMessagePrefix()} {fmt.format(*inputs)}" + if self._print_messages: + print(message, file=sys.stderr) + self._git_event_log.ErrorEvent(message, fmt) + + def _LogMessagePrefix(self): + """Returns the prefix string to be logged in each log message""" + return ( + f"repo superproject branch: {self._branch} url: {self._remote_url}" + ) + + def _LogError(self, fmt, *inputs): + """Logs error message to stderr and _git_event_log.""" + self._LogMessage(f"error: {fmt}", *inputs) + + def _LogWarning(self, fmt, *inputs): + """Logs warning message to stderr and _git_event_log.""" + self._LogMessage(f"warning: {fmt}", *inputs) + + def _Init(self): + """Sets up a local Git repository to get a copy of a superproject. + + Returns: + True if initialization is successful, or False. + """ + if not os.path.exists(self._superproject_path): + os.mkdir(self._superproject_path) + if not self._quiet and not os.path.exists(self._work_git): + print( + "%s: Performing initial setup for superproject; this might " + "take several minutes." % self._work_git + ) + cmd = ["init", "--bare", self._work_git_name] + p = GitCommand( + None, + cmd, + cwd=self._superproject_path, + capture_stdout=True, + capture_stderr=True, + ) + retval = p.Wait() + if retval: + self._LogWarning( + "git init call failed, command: git {}, " + "return code: {}, stderr: {}", + cmd, + retval, + p.stderr, + ) + return False + return True + + def _Fetch(self): + """Fetches a superproject for the manifest based on |_remote_url|. + + This runs git fetch which stores a local copy the superproject. + + Returns: + True if fetch is successful, or False. + """ + if not os.path.exists(self._work_git): + self._LogWarning("git fetch missing directory: {}", self._work_git) + return False + if not git_require((2, 28, 0)): + self._LogWarning( + "superproject requires a git version 2.28 or later" + ) + return False + cmd = [ + "fetch", + self._remote_url, + "--depth", + "1", + "--force", + "--no-tags", + "--filter", + "blob:none", + ] + + # Check if there is a local ref that we can pass to --negotiation-tip. + # If this is the first fetch, it does not exist yet. + # We use --negotiation-tip to speed up the fetch. Superproject branches + # do not share commits. So this lets git know it only needs to send + # commits reachable from the specified local refs. + rev_commit = GitRefs(self._work_git).get(f"refs/heads/{self.revision}") + if rev_commit: + cmd.extend(["--negotiation-tip", rev_commit]) + + if self._branch: + cmd += [self._branch + ":" + self._branch] + p = GitCommand( + None, + cmd, + cwd=self._work_git, + capture_stdout=True, + capture_stderr=True, + ) + retval = p.Wait() + if retval: + self._LogWarning( + "git fetch call failed, command: git {}, " + "return code: {}, stderr: {}", + cmd, + retval, + p.stderr, + ) + return False + return True + + def _LsTree(self): + """Gets the commit ids for all projects. + + Works only in git repositories. + + Returns: + data: data returned from 'git ls-tree ...' instead of None. + """ + if not os.path.exists(self._work_git): + self._LogWarning( + "git ls-tree missing directory: {}", self._work_git + ) + 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: + self._LogWarning( + "git ls-tree call failed, command: git {}, " + "return code: {}, stderr: {}", + cmd, + retval, + p.stderr, + ) + return data + + def Sync(self, git_event_log): + """Gets a local copy of a superproject for the manifest. + + Args: + git_event_log: an EventLog, for git tracing. + + Returns: + SyncResult + """ + self._git_event_log = git_event_log + if not self._manifest.superproject: + self._LogWarning( + "superproject tag is not defined in manifest: {}", + self._manifest.manifestFile, + ) + return SyncResult(False, False) + + _PrintBetaNotice() + + should_exit = True + if not self._remote_url: + self._LogWarning( + "superproject URL is not defined in manifest: {}", + self._manifest.manifestFile, + ) + return SyncResult(False, should_exit) + + if not self._Init(): + return SyncResult(False, should_exit) + if not self._Fetch(): + return SyncResult(False, should_exit) + if not self._quiet: + print( + "%s: Initial setup for superproject completed." % self._work_git + ) + return SyncResult(True, False) + + def _GetAllProjectsCommitIds(self): + """Get commit ids for all projects from superproject and save them. + + Commit ids are saved in _project_commit_ids. + + Returns: + CommitIdsResult + """ + sync_result = self.Sync(self._git_event_log) + if not sync_result.success: + return CommitIdsResult(None, sync_result.fatal) + + data = self._LsTree() + if not data: + self._LogWarning( + "git ls-tree failed to return data for manifest: {}", + self._manifest.manifestFile, + ) + return CommitIdsResult(None, True) + + # 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 # noqa: E501 + 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 CommitIdsResult(commit_ids, False) + + def _WriteManifestFile(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): + self._LogWarning( + "missing superproject directory: {}", self._superproject_path + ) + return None + manifest_str = self._manifest.ToXml( + groups=self._manifest.GetGroupsStr(), omit_local=True + ).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: + self._LogError("cannot write manifest to : {} {}", manifest_path, e) + return None + return manifest_path + + def _SkipUpdatingProjectRevisionId(self, project): + """Checks if a project's revision id needs to be updated or not. + + Revision id for projects from local manifest will not be updated. + + Args: + project: project whose revision id is being updated. + + Returns: + True if a project's revision id should not be updated, or False, + """ + path = project.relpath + if not path: + return True + # Skip the project with revisionId. + if project.revisionId: + return True + # Skip the project if it comes from the local manifest. + return project.manifest.IsFromLocalManifest(project) + + def UpdateProjectsRevisionId(self, projects, git_event_log): + """Update revisionId of every project in projects with the commit id. + + Args: + projects: a list of projects whose revisionId needs to be updated. + git_event_log: an EventLog, for git tracing. + + Returns: + UpdateProjectsResult + """ + self._git_event_log = git_event_log + commit_ids_result = self._GetAllProjectsCommitIds() + commit_ids = commit_ids_result.commit_ids + if not commit_ids: + return UpdateProjectsResult(None, commit_ids_result.fatal) + + projects_missing_commit_ids = [] + for project in projects: + if self._SkipUpdatingProjectRevisionId(project): + continue + path = project.relpath + commit_id = commit_ids.get(path) + if not commit_id: + projects_missing_commit_ids.append(path) + + # If superproject doesn't have a commit id for a project, then report an + # error event and continue as if do not use superproject is specified. + if projects_missing_commit_ids: + self._LogWarning( + "please file a bug using {} to report missing " + "commit_ids for: {}", + self._manifest.contactinfo.bugurl, + projects_missing_commit_ids, + ) + return UpdateProjectsResult(None, False) + + for project in projects: + if not self._SkipUpdatingProjectRevisionId(project): + project.SetRevisionId(commit_ids.get(project.relpath)) + + manifest_path = self._WriteManifestFile() + return UpdateProjectsResult(manifest_path, False) @functools.lru_cache(maxsize=10) def _PrintBetaNotice(): - """Print the notice of beta status.""" - print('NOTICE: --use-superproject is in beta; report any issues to the ' - 'address described in `repo version`', file=sys.stderr) + """Print the notice of beta status.""" + print( + "NOTICE: --use-superproject is in beta; report any issues to the " + "address described in `repo version`", + file=sys.stderr, + ) @functools.lru_cache(maxsize=None) def _UseSuperprojectFromConfiguration(): - """Returns the user choice of whether to use superproject.""" - user_cfg = RepoConfig.ForUser() - time_now = int(time.time()) - - user_value = user_cfg.GetBoolean('repo.superprojectChoice') - if user_value is not None: - user_expiration = user_cfg.GetInt('repo.superprojectChoiceExpire') - if user_expiration is None or user_expiration <= 0 or user_expiration >= time_now: - # TODO(b/190688390) - Remove prompt when we are comfortable with the new - # default value. - if user_value: - print(('You are currently enrolled in Git submodules experiment ' - '(go/android-submodules-quickstart). Use --no-use-superproject ' - 'to override.\n'), file=sys.stderr) - else: - print(('You are not currently enrolled in Git submodules experiment ' - '(go/android-submodules-quickstart). Use --use-superproject ' - 'to override.\n'), file=sys.stderr) - return user_value - - # We don't have an unexpired choice, ask for one. - system_cfg = RepoConfig.ForSystem() - system_value = system_cfg.GetBoolean('repo.superprojectChoice') - if system_value: - # The system configuration is proposing that we should enable the - # use of superproject. Treat the user as enrolled for two weeks. - # - # TODO(b/190688390) - Remove prompt when we are comfortable with the new - # default value. - userchoice = True - time_choiceexpire = time_now + (86400 * 14) - user_cfg.SetString('repo.superprojectChoiceExpire', str(time_choiceexpire)) - user_cfg.SetBoolean('repo.superprojectChoice', userchoice) - print('You are automatically enrolled in Git submodules experiment ' - '(go/android-submodules-quickstart) for another two weeks.\n', - file=sys.stderr) - return True - - # For all other cases, we would not use superproject by default. - return False + """Returns the user choice of whether to use superproject.""" + user_cfg = RepoConfig.ForUser() + time_now = int(time.time()) + + user_value = user_cfg.GetBoolean("repo.superprojectChoice") + if user_value is not None: + user_expiration = user_cfg.GetInt("repo.superprojectChoiceExpire") + if ( + user_expiration is None + or user_expiration <= 0 + or user_expiration >= time_now + ): + # TODO(b/190688390) - Remove prompt when we are comfortable with the + # new default value. + if user_value: + print( + ( + "You are currently enrolled in Git submodules " + "experiment (go/android-submodules-quickstart). Use " + "--no-use-superproject to override.\n" + ), + file=sys.stderr, + ) + else: + print( + ( + "You are not currently enrolled in Git submodules " + "experiment (go/android-submodules-quickstart). Use " + "--use-superproject to override.\n" + ), + file=sys.stderr, + ) + return user_value + + # We don't have an unexpired choice, ask for one. + system_cfg = RepoConfig.ForSystem() + system_value = system_cfg.GetBoolean("repo.superprojectChoice") + if system_value: + # The system configuration is proposing that we should enable the + # use of superproject. Treat the user as enrolled for two weeks. + # + # TODO(b/190688390) - Remove prompt when we are comfortable with the new + # default value. + userchoice = True + time_choiceexpire = time_now + (86400 * 14) + user_cfg.SetString( + "repo.superprojectChoiceExpire", str(time_choiceexpire) + ) + user_cfg.SetBoolean("repo.superprojectChoice", userchoice) + print( + "You are automatically enrolled in Git submodules experiment " + "(go/android-submodules-quickstart) for another two weeks.\n", + file=sys.stderr, + ) + return True + + # For all other cases, we would not use superproject by default. + return False def PrintMessages(use_superproject, manifest): - """Returns a boolean if error/warning messages are to be printed. + """Returns a boolean if error/warning messages are to be printed. - Args: - use_superproject: option value from optparse. - manifest: manifest to use. - """ - return use_superproject is not None or bool(manifest.superproject) + Args: + use_superproject: option value from optparse. + manifest: manifest to use. + """ + return use_superproject is not None or bool(manifest.superproject) def UseSuperproject(use_superproject, manifest): - """Returns a boolean if use-superproject option is enabled. + """Returns a boolean if use-superproject option is enabled. - Args: - use_superproject: option value from optparse. - manifest: manifest to use. + Args: + use_superproject: option value from optparse. + manifest: manifest to use. - Returns: - Whether the superproject should be used. - """ + Returns: + Whether the superproject should be used. + """ - if not manifest.superproject: - # This (sub) manifest does not have a superproject definition. - return False - elif use_superproject is not None: - return use_superproject - else: - client_value = manifest.manifestProject.use_superproject - if client_value is not None: - return client_value - elif manifest.superproject: - return _UseSuperprojectFromConfiguration() + if not manifest.superproject: + # This (sub) manifest does not have a superproject definition. + return False + elif use_superproject is not None: + return use_superproject else: - return False + client_value = manifest.manifestProject.use_superproject + if client_value is not None: + return client_value + elif manifest.superproject: + return _UseSuperprojectFromConfiguration() + else: + return False -- cgit v1.2.3-54-g00ecf