blob: 2d2b4c68709e990e503067905029f24a5b0d5fee [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 Google LLC
#
# 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.
"""Download from gerrit all the changes of the specified topic. The new
branches will be called TOPIC. If a project has multiple changes, they must
all form a parent-child chain, and only the last child will be downloaded.
"""
import argparse
import http
import logging
import subprocess
import sys
import tempfile
from louhi.common import gerrit
from louhi.common.utils import run
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def get_parser():
"""Constract the command line parser."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-b',
'--branch',
type=str,
help='branch name to use, instead of TOPIC.')
parser.add_argument('--force',
action='store_true',
help='delete the branch if it already exists.')
parser.add_argument(
'-s',
'--status',
type=str,
choices=[
'abandoned', 'closed', 'merged', 'new', 'pending', 'reviewed',
'open'
],
default='open',
help='(default: open) limit to changes with matching status; '
'see also --no-status.')
parser.add_argument(
'--no-status',
dest="status",
action='store_const',
const=None,
help='don\'t limit to changes by status; see also --status.')
parser.add_argument('--repo-path',
metavar='PATH',
type=str,
default='repo',
help='(default: repo) path to the repo executable.')
parser.add_argument('topic', metavar='TOPIC')
return parser
def main():
"""The main function."""
args = get_parser().parse_args()
branch = args.branch if args.branch else args.topic
try:
git_cookie_file = run(['git', 'config', 'http.cookieFile'],
verbose=False).read().rstrip()
except subprocess.CalledProcessError:
logging.error('The git option http.cookieFile is not set.')
return 1
cookies = http.cookiejar.MozillaCookieJar()
with tempfile.NamedTemporaryFile('w', encoding='ascii') as temp_file:
# NB: MozillaCookieJar requires the file to start with the
# following line, so we add it in a temp file.
temp_file.write('# Netscape HTTP Cookie File\n')
with open(git_cookie_file, 'r', encoding='ascii') as cookie_file:
temp_file.write(cookie_file.read())
temp_file.flush()
cookies.load(temp_file.name)
changes = gerrit.query_topic(args.topic,
args.status,
access_cookie=cookies)
if not changes:
logging.error('No changes to download.')
return 1
branch_exists = run([args.repo_path, 'forall'] + list(changes) + [
'--command', f'! git rev-parse --verify "{branch}" &>/dev/null '
'|| echo "$REPO_PROJECT"'
],
verbose=False).read().split('\n')[:-1]
if branch_exists:
if not args.force:
logging.error(
'The branch %s alread exists in %s (use --force to '
'overwrite it.)', branch, branch_exists)
return 1
logging.info('Deleting branch %s from %s', branch, branch_exists)
run([args.repo_path, 'forall'] + branch_exists + [
'--command',
f'if git rev-parse --verify "{branch}" &>/dev/null; then'
' git checkout --detach HEAD;'
f' git branch --delete "{branch}" --force;'
'fi'
])
logging.info('Downloading changes: %s', dict(changes))
run([args.repo_path, 'download', f'--branch={branch}', '--verbose'] +
[str(part) for change in changes.items() for part in change])
return 0
if __name__ == '__main__':
sys.exit(main())