diff --git a/git_updater.py b/git_updater.py index bde48a70d498c0b18cfb4ff80f7548891e0e3a82..4c878c847cfa9431326b4a8a96f6b8ca6882dce4 100644 --- a/git_updater.py +++ b/git_updater.py @@ -19,6 +19,7 @@ import datetime import fileutils import git_utils import metadata_pb2 # pylint: disable=import-error +import updater_utils class GitUpdater(): @@ -32,7 +33,8 @@ class GitUpdater(): self.upstream_url = url self.upstream_remote_name = None self.android_remote_name = None - self.latest_commit = None + self.new_version = None + self.merge_from = None def _setup_remote(self): remotes = git_utils.list_remotes(self.proj_path) @@ -56,6 +58,25 @@ class GitUpdater(): """Checks upstream and returns whether a new version is available.""" self._setup_remote() + if git_utils.is_commit(self.metadata.third_party.version): + # Update to remote head. + return self._check_head() + + # Update to latest version tag. + return self._check_tag() + + def _check_tag(self): + tags = git_utils.list_remote_tags(self.proj_path, + self.upstream_remote_name) + current_ver = self.metadata.third_party.version + self.new_version = updater_utils.get_latest_version( + current_ver, tags) + self.merge_from = self.new_version + print('Current version: {}. Latest version: {}'.format( + current_ver, self.new_version), end='') + return self.new_version != current_ver + + def _check_head(self): commits = git_utils.get_commits_ahead( self.proj_path, self.upstream_remote_name + '/master', self.android_remote_name + '/master') @@ -63,7 +84,19 @@ class GitUpdater(): if not commits: return False - self.latest_commit = commits[0] + self.new_version = commits[0] + + # See whether we have a local upstream. + branches = git_utils.list_remote_branches( + self.proj_path, self.android_remote_name) + upstreams = [ + branch for branch in branches if branch.startswith('upstream-')] + if upstreams: + self.merge_from = '{}/{}'.format( + self.android_remote_name, upstreams[0]) + else: + self.merge_from = 'update_origin/master' + commit_time = git_utils.get_commit_time(self.proj_path, commits[-1]) time_behind = datetime.datetime.now() - commit_time print('{} commits ({} days) behind.'.format( @@ -73,7 +106,7 @@ class GitUpdater(): def _write_metadata(self, path): updated_metadata = metadata_pb2.MetaData() updated_metadata.CopyFrom(self.metadata) - updated_metadata.third_party.version = self.latest_commit + updated_metadata.third_party.version = self.new_version fileutils.write_metadata(path, updated_metadata) def update(self): @@ -81,32 +114,19 @@ class GitUpdater(): Has to call check() before this function. """ - # See whether we have a local upstream. - branches = git_utils.list_remote_branches( - self.proj_path, self.android_remote_name) - upstreams = [ - branch for branch in branches if branch.startswith('upstream-')] - if len(upstreams) == 1: - merge_branch = '{}/{}'.format( - self.android_remote_name, upstreams[0]) - elif not upstreams: - merge_branch = 'update_origin/master' - else: - raise ValueError('Ambiguous upstream branch. ' + upstreams) - upstream_branch = self.upstream_remote_name + '/master' commits = git_utils.get_commits_ahead( - self.proj_path, merge_branch, upstream_branch) + self.proj_path, self.merge_from, upstream_branch) if commits: - print('Warning! {} is {} commits ahead of {}. {}'.format( - merge_branch, len(commits), upstream_branch, commits)) + print('{} is {} commits ahead of {}. {}'.format( + self.merge_from, len(commits), upstream_branch, commits)) commits = git_utils.get_commits_ahead( - self.proj_path, upstream_branch, merge_branch) + self.proj_path, upstream_branch, self.merge_from) if commits: - print('Warning! {} is {} commits behind of {}.'.format( - merge_branch, len(commits), upstream_branch)) + print('{} is {} commits behind of {}.'.format( + self.merge_from, len(commits), upstream_branch)) self._write_metadata(self.proj_path) print(""" @@ -115,4 +135,4 @@ This tool only updates METADATA. Run the following command to update: To check all local changes: git diff {merge_branch} HEAD -""".format(merge_branch=merge_branch)) +""".format(merge_branch=self.merge_from)) diff --git a/git_utils.py b/git_utils.py index b2f6bbbf59757f140f78629ded1725d4ab8a143d..abfcea7192155a9025b62abed8dbdd5b3f931631 100644 --- a/git_utils.py +++ b/git_utils.py @@ -14,6 +14,7 @@ '''Helper functions to communicate with Git.''' import datetime +import re import subprocess @@ -81,3 +82,33 @@ def list_remote_branches(proj_path, remote_name): remote_path_len = len(remote_path) return [line[remote_path_len:] for line in stripped if line.startswith(remote_path)] + + +def _parse_remote_tag(line): + tag_prefix = 'refs/tags/' + tag_suffix = '^{}' + try: + line = line[line.index(tag_prefix):] + except ValueError: + return None + line = line[len(tag_prefix):] + if line.endswith(tag_suffix): + line = line[:-len(tag_suffix)] + return line + + +def list_remote_tags(proj_path, remote_name): + """Lists all tags for a remote.""" + out = _run(['git', "ls-remote", "--tags", remote_name], + cwd=proj_path) + lines = out.stdout.decode('utf-8').splitlines() + tags = [_parse_remote_tag(line) for line in lines] + return list(set(tags)) + + +COMMIT_PATTERN = r'^[a-f0-9]{40}$' +COMMIT_RE = re.compile(COMMIT_PATTERN) + + +def is_commit(commit): + return bool(COMMIT_RE.match(commit)) diff --git a/updater_utils.py b/updater_utils.py index e9d9620e7f304c88009d947ba4644bee89f3a806..cb1de54593a07e3b9283e0c8a665b3dd426465c9 100644 --- a/updater_utils.py +++ b/updater_utils.py @@ -14,6 +14,7 @@ """Helper functions for updaters.""" import os +import re import subprocess import sys @@ -55,3 +56,43 @@ def replace_package(source_dir, target_dir): sys.argv[0]), 'update_package.sh') subprocess.check_call(['bash', script_path, source_dir, target_dir]) + + +VERSION_PATTERN = (r'^(?P<prefix>[^\d]*)' + + r'(?P<version>\d+(\.\d+)*)' + + r'(?P<suffix>.*)$') +VERSION_RE = re.compile(VERSION_PATTERN) + + +def parse_version(version): + match = VERSION_RE.match(version) + if match is None: + raise ValueError('Invalid version.') + try: + return match.group('prefix', 'version', 'suffix') + except IndexError: + raise ValueError('Invalid version.') + + +def _match_and_get_version(prefix, suffix, version): + try: + version_prefix, version, version_suffix = parse_version(version) + except ValueError: + return [] + + if version_prefix != prefix or version_suffix != suffix: + return [] + + return [int(v) for v in version.split('.')] + + +def get_latest_version(old_version, version_list): + old_prefix, _, old_suffix = parse_version(old_version) + + latest = max(version_list + [old_version], + key=lambda ver: _match_and_get_version( + old_prefix, old_suffix, ver)) + if not latest: + return None + + return latest