Doug Hellmann 83c16a48fe make list-changes smarter about the "previous" version
The old implementation of list-changes assumed that the deliverable file
included all releases and that they were in order. That assumption does
not hold for independent projects, where the history might not be there
and new entries might be from different branches than the previous
entries, all in the same file. This change uses git to determine the
previous tag for each repository, then shows the right diffs based on
that instead of the assumed tag from the deliverable file.

Change-Id: I122a6ec1792a07cdb23f09a880110dff2446555e
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2016-08-15 16:20:01 -04:00

146 lines
4.4 KiB
Python

# All Rights Reserved.
#
# 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.
import os
import os.path
import subprocess
import requests
# Disable warnings about insecure connections.
from requests.packages import urllib3
urllib3.disable_warnings()
CGIT_TEMPLATE = 'http://git.openstack.org/cgit/%s/commit/?id=%s'
def find_modified_deliverable_files():
"Return a list of files modified by the most recent commit."
results = subprocess.check_output(
['git', 'diff', '--name-only', '--pretty=format:', 'HEAD^']
)
filenames = [
l.strip()
for l in results.splitlines()
if l.startswith('deliverables/')
]
return filenames
def commit_exists(repo, ref):
"""Return boolean specifying whether the reference exists in the repository.
Uses a cgit query instead of looking locally to avoid cloning a
repository or having Depends-On settings in a commit message allow
someone to fool the check.
"""
url = CGIT_TEMPLATE % (repo, ref)
response = requests.get(url)
missing_commit = (
(response.status_code // 100 != 2) or 'Bad object id' in response.text
)
return not missing_commit
def clone_repo(workdir, repo):
"Check out the code."
dest = os.path.join(workdir, repo)
if os.path.exists(dest):
return
cmd = [
'zuul-cloner',
'--workspace', workdir,
]
cache_dir = os.environ.get('ZUUL_CACHE_DIR', '/opt/git')
if cache_dir and os.path.exists(cache_dir):
cmd.extend(['--cache-dir', cache_dir])
cmd.extend([
'git://git.openstack.org',
repo,
])
subprocess.check_call(cmd)
# Force an update, just in case the local version is still out of
# date.
print('Updating newly cloned repository in %s' % dest)
subprocess.check_call(
['git', 'fetch', '-v', '--tags'],
cwd=dest,
)
def sha_for_tag(workdir, repo, version):
"""Return the SHA for a given tag
"""
# git log 2.3.11 -n 1 --pretty=format:%H
try:
actual_sha = subprocess.check_output(
['git', 'log', str(version), '-n', '1', '--pretty=format:%H'],
cwd=os.path.join(workdir, repo),
stderr=subprocess.STDOUT,
)
actual_sha = actual_sha.strip()
except subprocess.CalledProcessError as e:
print('ERROR getting SHA for tag %r: %s [%s]' %
(version, e, e.output.strip()))
actual_sha = ''
return actual_sha
def check_ancestry(workdir, repo, old_version, sha):
"Check if the SHA is in the ancestry of the previous version."
try:
ancestors = subprocess.check_output(
['git', 'log', '--oneline', '--ancestry-path',
'%s..%s' % (old_version, sha)],
cwd=os.path.join(workdir, repo),
).strip()
return bool(ancestors)
except subprocess.CalledProcessError as e:
print('ERROR checking ancestry: %s [%s]' % (e, e.output.strip()))
return False
def get_latest_tag(workdir, repo, sha=None):
cmd = ['git', 'describe', '--abbrev=0']
if sha is not None:
cmd.append(sha)
try:
return subprocess.check_output(
cmd,
cwd=os.path.join(workdir, repo),
stderr=subprocess.STDOUT,
).strip()
except subprocess.CalledProcessError as e:
print('WARNING failed to retrieve latest tag: %s [%s]' %
(e, e.output.strip()))
return None
def get_branches(workdir, repo):
try:
output = subprocess.check_output(
['git', 'branch', '-a'],
cwd=os.path.join(workdir, repo),
stderr=subprocess.STDOUT,
).strip()
return [
branch
for branch in output.split()
]
except subprocess.CalledProcessError as e:
print('ERROR failed to retrieve list of branches: %s [%s]' %
(e, e.output.strip()))
return []