aboutsummaryrefslogtreecommitdiff
path: root/contrib/gcc-changelog/git_commit.py
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/gcc-changelog/git_commit.py')
-rwxr-xr-xcontrib/gcc-changelog/git_commit.py87
1 files changed, 67 insertions, 20 deletions
diff --git a/contrib/gcc-changelog/git_commit.py b/contrib/gcc-changelog/git_commit.py
index 43fa7f4..d1646bd 100755
--- a/contrib/gcc-changelog/git_commit.py
+++ b/contrib/gcc-changelog/git_commit.py
@@ -19,8 +19,9 @@
import difflib
import os
import re
+import sys
-changelog_locations = {
+default_changelog_locations = {
'c++tools',
'config',
'contrib',
@@ -155,7 +156,9 @@ author_line_regex = \
re.compile(r'^(?P<datetime>\d{4}-\d{2}-\d{2})\ {2}(?P<name>.* <.*>)')
additional_author_regex = re.compile(r'^\t(?P<spaces>\ *)?(?P<name>.* <.*>)')
changelog_regex = re.compile(r'^(?:[fF]or +)?([a-z0-9+-/]*)ChangeLog:?')
-pr_regex = re.compile(r'\tPR (?P<component>[a-z+-]+\/)?([0-9]+)$')
+subject_pr_regex = re.compile(r'(^|\W)PR\s+(?P<component>[a-zA-Z+-]+)/(?P<pr>\d{4,7})')
+subject_pr2_regex = re.compile(r'[(\[]PR\s*(?P<pr>\d{4,7})[)\]]')
+pr_regex = re.compile(r'\tPR (?P<component>[a-z+-]+\/)?(?P<pr>[0-9]+)$')
dr_regex = re.compile(r'\tDR ([0-9]+)$')
star_prefix_regex = re.compile(r'\t\*(?P<spaces>\ *)(?P<content>.*)')
end_of_location_regex = re.compile(r'[\[<(:]')
@@ -200,7 +203,7 @@ class Error:
def __repr__(self):
s = self.message
if self.line:
- s += ':"%s"' % self.line
+ s += ': "%s"' % self.line
return s
@@ -287,7 +290,7 @@ class GitInfo:
class GitCommit:
- def __init__(self, info, strict=True, commit_to_info_hook=None):
+ def __init__(self, info, commit_to_info_hook=None, ref_name=None):
self.original_info = info
self.info = info
self.message = None
@@ -297,14 +300,19 @@ class GitCommit:
self.top_level_authors = []
self.co_authors = []
self.top_level_prs = []
+ self.subject_prs = set()
self.cherry_pick_commit = None
self.revert_commit = None
self.commit_to_info_hook = commit_to_info_hook
+ self.init_changelog_locations(ref_name)
# Skip Update copyright years commits
if self.info.lines and self.info.lines[0] == 'Update copyright years.':
return
+ if self.info.lines and len(self.info.lines) > 1 and self.info.lines[1]:
+ self.errors.append(Error('Expected empty second line in commit message', info.lines[0]))
+
# Identify first if the commit is a Revert commit
for line in self.info.lines:
m = revert_regex.match(line)
@@ -314,6 +322,19 @@ class GitCommit:
if self.revert_commit:
self.info = self.commit_to_info_hook(self.revert_commit)
+ # The following happens for get_email.py:
+ if not self.info:
+ return
+
+ # Extract PR numbers form the subject line
+ # Match either [PRnnnn] / (PRnnnn) or PR component/nnnn
+ if self.info.lines and not self.revert_commit:
+ self.subject_prs = {m.group('pr') for m in subject_pr2_regex.finditer(info.lines[0])}
+ for m in subject_pr_regex.finditer(info.lines[0]):
+ if not m.group('component') in bug_components:
+ self.errors.append(Error('invalid PR component in subject', info.lines[0]))
+ self.subject_prs.add(m.group('pr'))
+
# Allow complete deletion of ChangeLog files in a commit
project_files = [f for f in self.info.modified_files
if (self.is_changelog_filename(f[0], allow_suffix=True) and f[1] != 'D')
@@ -323,10 +344,12 @@ class GitCommit:
if len(project_files) == len(self.info.modified_files):
# All modified files are only MISC files
return
- elif project_files and strict:
- self.errors.append(Error('ChangeLog, DATESTAMP, BASE-VER and '
- 'DEV-PHASE updates should be done '
- 'separately from normal commits'))
+ elif project_files:
+ err = 'ChangeLog, DATESTAMP, BASE-VER and DEV-PHASE updates ' \
+ 'should be done separately from normal commits\n' \
+ '(note: ChangeLog entries will be automatically ' \
+ 'added by a cron job)'
+ self.errors.append(Error(err))
return
all_are_ignored = (len(project_files) + len(ignored_files)
@@ -342,6 +365,9 @@ class GitCommit:
if not self.errors:
self.check_mentioned_files()
self.check_for_correct_changelog()
+ if self.subject_prs:
+ self.errors.append(Error('PR %s in subject but not in changelog' %
+ ', '.join(self.subject_prs), self.info.lines[0]))
@property
def success(self):
@@ -361,15 +387,14 @@ class GitCommit:
else:
return False
- @classmethod
- def find_changelog_location(cls, name):
+ def find_changelog_location(self, name):
if name.startswith('\t'):
name = name[1:]
if name.endswith(':'):
name = name[:-1]
if name.endswith('/'):
name = name[:-1]
- return name if name in changelog_locations else None
+ return name if name in self.changelog_locations else None
@classmethod
def format_git_author(cls, author):
@@ -389,6 +414,17 @@ class GitCommit:
modified_files.append((parts[2], 'A'))
return modified_files
+ def init_changelog_locations(self, ref_name):
+ self.changelog_locations = list(default_changelog_locations)
+ if ref_name:
+ version = sys.maxsize
+ if 'releases/gcc-' in ref_name:
+ version = int(ref_name.split('-')[-1])
+ if version >= 12:
+ # HSA and BRIG were removed in GCC 12
+ self.changelog_locations.remove('gcc/brig')
+ self.changelog_locations.remove('libhsail-rt')
+
def parse_lines(self, all_are_ignored):
body = self.info.lines
@@ -397,7 +433,8 @@ class GitCommit:
continue
if (changelog_regex.match(b) or self.find_changelog_location(b)
or star_prefix_regex.match(b) or pr_regex.match(b)
- or dr_regex.match(b) or author_line_regex.match(b)):
+ or dr_regex.match(b) or author_line_regex.match(b)
+ or b.lower().startswith(CO_AUTHORED_BY_PREFIX)):
self.changes = body[i:]
return
if not all_are_ignored:
@@ -415,8 +452,10 @@ class GitCommit:
if line != line.rstrip():
self.errors.append(Error('trailing whitespace', line))
if len(line.replace('\t', ' ' * TAB_WIDTH)) > LINE_LIMIT:
- self.errors.append(Error('line exceeds %d character limit'
- % LINE_LIMIT, line))
+ # support long filenames
+ if not line.startswith('\t* ') or not line.endswith(':') or ' ' in line[3:-1]:
+ self.errors.append(Error('line exceeds %d character limit'
+ % LINE_LIMIT, line))
m = changelog_regex.match(line)
if m:
last_entry = ChangeLogEntry(m.group(1).rstrip('/'),
@@ -443,7 +482,9 @@ class GitCommit:
else:
author_tuple = (m.group('name'), None)
elif pr_regex.match(line):
- component = pr_regex.match(line).group('component')
+ m = pr_regex.match(line)
+ component = m.group('component')
+ pr = m.group('pr')
if not component:
self.errors.append(Error('missing PR component', line))
continue
@@ -452,6 +493,8 @@ class GitCommit:
continue
else:
pr_line = line.lstrip()
+ if pr in self.subject_prs:
+ self.subject_prs.remove(pr)
elif dr_regex.match(line):
pr_line = line.lstrip()
@@ -584,7 +627,7 @@ class GitCommit:
for file in entry.files:
location = self.get_file_changelog_location(file)
if (location == ''
- or (location and location in changelog_locations)):
+ or (location and location in self.changelog_locations)):
if changelog and changelog != location:
msg = 'could not deduce ChangeLog file, ' \
'not unique location'
@@ -604,11 +647,10 @@ class GitCommit:
return True
return False
- @classmethod
- def get_changelog_by_path(cls, path):
+ def get_changelog_by_path(self, path):
components = path.split('/')
while components:
- if '/'.join(components) in changelog_locations:
+ if '/'.join(components) in self.changelog_locations:
break
components = components[:-1]
return '/'.join(components)
@@ -627,7 +669,12 @@ class GitCommit:
assert not entry.folder.endswith('/')
for file in entry.files:
if not self.is_changelog_filename(file):
- mentioned_files.add(os.path.join(entry.folder, file))
+ item = os.path.join(entry.folder, file)
+ if item in mentioned_files:
+ msg = 'same file specified multiple times'
+ self.errors.append(Error(msg, file))
+ else:
+ mentioned_files.add(item)
for pattern in entry.file_patterns:
mentioned_patterns.append(os.path.join(entry.folder, pattern))