gcc-changelog: sync from master.

contrib/ChangeLog:

	* gcc-changelog/git_check_commit.py: New file.
	* gcc-changelog/git_commit.py: New file.
	* gcc-changelog/git_email.py: New file.
	* gcc-changelog/git_repository.py: New file.
	* gcc-changelog/git_update_version.py: New file.
	* gcc-changelog/test_email.py: New file.
	* gcc-changelog/test_patches.txt: New file.
parent 01bea59d
#!/usr/bin/env python3
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>. */
import argparse
from git_repository import parse_git_revisions
parser = argparse.ArgumentParser(description='Check git ChangeLog format '
'of a commit')
parser.add_argument('revisions', default='HEAD', nargs='?',
help='Git revisions (e.g. hash~5..hash or just hash)')
parser.add_argument('-g', '--git-path', default='.',
help='Path to git repository')
parser.add_argument('-p', '--print-changelog', action='store_true',
help='Print final changelog entires')
parser.add_argument('-n', '--non-strict-mode', action='store_true',
help='Use non-strict mode (allow changes in ChangeLog and '
'other automatically updated files).')
args = parser.parse_args()
retval = 0
for git_commit in parse_git_revisions(args.git_path, args.revisions,
not args.non_strict_mode):
res = 'OK' if git_commit.success else 'FAILED'
print('Checking %s: %s' % (git_commit.original_info.hexsha, res))
if git_commit.success:
if args.print_changelog:
git_commit.print_output()
else:
for error in git_commit.errors:
print('ERR: %s' % error)
retval = 1
exit(retval)
#!/usr/bin/env python3
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>. */
import os
import sys
from itertools import takewhile
from dateutil.parser import parse
from git_commit import GitCommit, GitInfo
from unidiff import PatchSet
DATE_PREFIX = 'Date: '
FROM_PREFIX = 'From: '
class GitEmail(GitCommit):
def __init__(self, filename, strict=False):
self.filename = filename
diff = PatchSet.from_filename(filename)
date = None
author = None
with open(self.filename, 'r') as f:
lines = f.read().splitlines()
lines = list(takewhile(lambda line: line != '---', lines))
for line in lines:
if line.startswith(DATE_PREFIX):
date = parse(line[len(DATE_PREFIX):])
elif line.startswith(FROM_PREFIX):
author = GitCommit.format_git_author(line[len(FROM_PREFIX):])
header = list(takewhile(lambda line: line != '', lines))
body = lines[len(header) + 1:]
modified_files = []
for f in diff:
# Strip "a/" and "b/" prefixes
source = f.source_file[2:]
target = f.target_file[2:]
if f.is_added_file:
t = 'A'
elif f.is_removed_file:
t = 'D'
elif f.is_rename:
# Consider that renamed files are two operations: the deletion
# of the original name and the addition of the new one.
modified_files.append((source, 'D'))
t = 'A'
else:
t = 'M'
modified_files.append((target, t))
git_info = GitInfo(None, date, author, body, modified_files)
super().__init__(git_info, strict=strict,
commit_to_info_hook=lambda x: None)
# With zero arguments, process every patch file in the ./patches directory.
# With one argument, process the named patch file.
# Patch files must be in 'git format-patch' format.
if __name__ == '__main__':
if len(sys.argv) == 1:
allfiles = []
for root, _dirs, files in os.walk('patches'):
for f in files:
full = os.path.join(root, f)
allfiles.append(full)
success = 0
for full in sorted(allfiles):
email = GitEmail(full, False)
print(email.filename)
if email.success:
success += 1
print(' OK')
else:
for error in email.errors:
print(' ERR: %s' % error)
print()
print('Successfully parsed: %d/%d' % (success, len(allfiles)))
else:
email = GitEmail(sys.argv[1], False)
if email.success:
print('OK')
email.print_output()
else:
if not email.info.lines:
print('Error: patch contains no parsed lines', file=sys.stderr)
email.print_errors()
sys.exit(1)
#!/usr/bin/env python3
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>. */
from datetime import datetime
try:
from git import Repo
except ImportError:
print('Cannot import GitPython package, please install the package:')
print(' Fedora, openSUSE: python3-GitPython')
print(' Debian, Ubuntu: python3-git')
exit(1)
from git_commit import GitCommit, GitInfo
def parse_git_revisions(repo_path, revisions, strict=False):
repo = Repo(repo_path)
def commit_to_info(commit):
try:
c = repo.commit(commit)
diff = repo.commit(commit + '~').diff(commit)
modified_files = []
for file in diff:
if hasattr(file, 'renamed_file'):
is_renamed = file.renamed_file
else:
is_renamed = file.renamed
if file.new_file:
t = 'A'
elif file.deleted_file:
t = 'D'
elif is_renamed:
# Consider that renamed files are two operations:
# the deletion of the original name
# and the addition of the new one.
modified_files.append((file.a_path, 'D'))
t = 'A'
else:
t = 'M'
modified_files.append((file.b_path, t))
date = datetime.utcfromtimestamp(c.committed_date)
author = '%s <%s>' % (c.author.name, c.author.email)
git_info = GitInfo(c.hexsha, date, author,
c.message.split('\n'), modified_files)
return git_info
except ValueError:
return None
parsed_commits = []
if '..' in revisions:
commits = list(repo.iter_commits(revisions))
else:
commits = [repo.commit(revisions)]
for commit in commits:
git_commit = GitCommit(commit_to_info(commit.hexsha), strict=strict,
commit_to_info_hook=commit_to_info)
parsed_commits.append(git_commit)
return parsed_commits
#!/usr/bin/env python3
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>. */
import argparse
import datetime
import os
from git import Repo
from git_repository import parse_git_revisions
current_timestamp = datetime.datetime.now().strftime('%Y%m%d\n')
def read_timestamp(path):
with open(path) as f:
return f.read()
def prepend_to_changelog_files(repo, folder, git_commit, add_to_git):
if not git_commit.success:
for error in git_commit.errors:
print(error)
raise AssertionError()
for entry, output in git_commit.to_changelog_entries(use_commit_ts=True):
full_path = os.path.join(folder, entry, 'ChangeLog')
print('writting to %s' % full_path)
if os.path.exists(full_path):
with open(full_path) as f:
content = f.read()
else:
content = ''
with open(full_path, 'w+') as f:
f.write(output)
if content:
f.write('\n\n')
f.write(content)
if add_to_git:
repo.git.add(full_path)
active_refs = ['master', 'releases/gcc-8', 'releases/gcc-9', 'releases/gcc-10']
parser = argparse.ArgumentParser(description='Update DATESTAMP and generate '
'ChangeLog entries')
parser.add_argument('-g', '--git-path', default='.',
help='Path to git repository')
parser.add_argument('-p', '--push', action='store_true',
help='Push updated active branches')
parser.add_argument('-d', '--dry-mode',
help='Generate patch for ChangeLog entries and do it'
' even if DATESTAMP is unchanged; folder argument'
' is expected')
parser.add_argument('-c', '--current', action='store_true',
help='Modify current branch (--push argument is ignored)')
args = parser.parse_args()
repo = Repo(args.git_path)
origin = repo.remotes['origin']
def update_current_branch():
commit = repo.head.commit
commit_count = 1
while commit:
if (commit.author.email == 'gccadmin@gcc.gnu.org'
and commit.message.strip() == 'Daily bump.'):
break
# We support merge commits but only with 2 parensts
assert len(commit.parents) <= 2
commit = commit.parents[-1]
commit_count += 1
print('%d revisions since last Daily bump' % commit_count)
datestamp_path = os.path.join(args.git_path, 'gcc/DATESTAMP')
if (read_timestamp(datestamp_path) != current_timestamp
or args.dry_mode or args.current):
head = repo.head.commit
# if HEAD is a merge commit, start with second parent
# (branched that is being merged into the current one)
assert len(head.parents) <= 2
if len(head.parents) == 2:
head = head.parents[1]
commits = parse_git_revisions(args.git_path, '%s..%s'
% (commit.hexsha, head.hexsha))
for git_commit in reversed(commits):
prepend_to_changelog_files(repo, args.git_path, git_commit,
not args.dry_mode)
if args.dry_mode:
diff = repo.git.diff('HEAD')
patch = os.path.join(args.dry_mode,
branch.name.split('/')[-1] + '.patch')
with open(patch, 'w+') as f:
f.write(diff)
print('branch diff written to %s' % patch)
repo.git.checkout(force=True)
else:
# update timestamp
print('DATESTAMP will be changed:')
with open(datestamp_path, 'w+') as f:
f.write(current_timestamp)
repo.git.add(datestamp_path)
if not args.current:
repo.index.commit('Daily bump.')
if args.push:
repo.git.push('origin', branch)
print('branch is pushed')
else:
print('DATESTAMP unchanged')
if args.current:
print('=== Working on the current branch ===', flush=True)
update_current_branch()
else:
for ref in origin.refs:
assert ref.name.startswith('origin/')
name = ref.name[len('origin/'):]
if name in active_refs:
if name in repo.branches:
branch = repo.branches[name]
else:
branch = repo.create_head(name, ref).set_tracking_branch(ref)
print('=== Working on: %s ===' % branch, flush=True)
branch.checkout()
origin.pull(rebase=True)
print('branch pulled and checked out')
update_current_branch()
assert not repo.index.diff(None)
print('branch is done\n', flush=True)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment