| # Copyright (C) 2018 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. |
| '''Helper functions to communicate with Git.''' |
| |
| import datetime |
| import re |
| import subprocess |
| |
| |
| def _run(cmd, cwd, redirect=True): |
| """Runs a command with stdout and stderr redirected.""" |
| out = subprocess.PIPE if redirect else None |
| return subprocess.run(cmd, stdout=out, stderr=out, |
| check=True, cwd=cwd) |
| |
| |
| def fetch(proj_path, remote_names): |
| """Runs git fetch. |
| |
| Args: |
| proj_path: Path to Git repository. |
| remote_names: Array of string to specify remote names. |
| """ |
| _run(['git', 'fetch', '--multiple'] + remote_names, cwd=proj_path) |
| |
| |
| def add_remote(proj_path, name, url): |
| """Adds a git remote. |
| |
| Args: |
| proj_path: Path to Git repository. |
| name: Name of the new remote. |
| url: Url of the new remote. |
| """ |
| _run(['git', 'remote', 'add', name, url], cwd=proj_path) |
| |
| |
| def list_remotes(proj_path): |
| """Lists all Git remotes. |
| |
| Args: |
| proj_path: Path to Git repository. |
| |
| Returns: |
| A dict from remote name to remote url. |
| """ |
| out = _run(['git', 'remote', '-v'], proj_path) |
| lines = out.stdout.decode('utf-8').splitlines() |
| return dict([line.split()[0:2] for line in lines]) |
| |
| |
| def get_commits_ahead(proj_path, branch, base_branch): |
| """Lists commits in `branch` but not `base_branch`.""" |
| out = _run(['git', 'rev-list', '--left-only', '--ancestry-path', |
| '{}...{}'.format(branch, base_branch)], |
| proj_path) |
| return out.stdout.decode('utf-8').splitlines() |
| |
| |
| def get_commit_time(proj_path, commit): |
| """Gets commit time of one commit.""" |
| out = _run(['git', 'show', '-s', '--format=%ct', commit], cwd=proj_path) |
| return datetime.datetime.fromtimestamp(int(out.stdout)) |
| |
| |
| def list_remote_branches(proj_path, remote_name): |
| """Lists all branches for a remote.""" |
| out = _run(['git', 'branch', '-r'], cwd=proj_path) |
| lines = out.stdout.decode('utf-8').splitlines() |
| stripped = [line.strip() for line in lines] |
| remote_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): |
| """Whether a string looks like a SHA1 hash.""" |
| return bool(COMMIT_RE.match(commit)) |
| |
| |
| def merge(proj_path, branch): |
| """Merges a branch.""" |
| try: |
| out = _run(['git', 'merge', branch, '--no-commit'], |
| cwd=proj_path) |
| except subprocess.CalledProcessError: |
| # Merge failed. Error is already written to console. |
| subprocess.run(['git', 'merge', '--abort'], cwd=proj_path) |
| raise |
| |
| |
| def add_file(proj_path, file_name): |
| """Stages a file.""" |
| _run(['git', 'add', file_name], cwd=proj_path) |
| |
| |
| def delete_branch(proj_path, branch_name): |
| """Force delete a branch.""" |
| _run(['git', 'branch', '-D', branch_name], cwd=proj_path) |
| |
| |
| def start_branch(proj_path, branch_name): |
| """Starts a new repo branch.""" |
| _run(['repo', 'start', branch_name], cwd=proj_path) |
| |
| |
| def commit(proj_path, message): |
| """Commits changes.""" |
| _run(['git', 'commit', '-m', message], cwd=proj_path) |
| |
| |
| def checkout(proj_path, branch_name): |
| """Checkouts a branch.""" |
| _run(['git', 'checkout', branch_name], cwd=proj_path) |
| |
| |
| def push(proj_path, remote_name): |
| """Pushes change to remote.""" |
| return _run(['git', 'push', remote_name, 'HEAD:refs/for/master'], |
| cwd=proj_path, redirect=False) |