aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThierry Reding <treding@nvidia.com>2023-01-05 11:48:31 +0100
committerThierry Reding <treding@nvidia.com>2023-01-05 11:48:31 +0100
commitb230ac47bbf9ee6f7b7f390ed3f9a91b753c1975 (patch)
tree35518819ef0ca9cc075fa0bbf8e80fd8cad42c2a
parent422b26970b4ebe890d2a2a8fd6ad225314c1e1f4 (diff)
downloadmaint-scripts-b230ac47bbf9ee6f7b7f390ed3f9a91b753c1975.tar.gz
tms: Add check command
This new command can be used to run various checks on the branches, such as checking for valid commit IDs and Signed-off-by: lines. Signed-off-by: Thierry Reding <treding@nvidia.com>
-rwxr-xr-xtms127
1 files changed, 126 insertions, 1 deletions
diff --git a/tms b/tms
index ba05f4b..0bc5ded 100755
--- a/tms
+++ b/tms
@@ -1,6 +1,6 @@
#!/usr/bin/python3
-import argparse, configparser, email.message, git, io, logging, math, os
+import argparse, binascii, configparser, email.message, git, io, logging, math, os
import os.path, re, subprocess, sys, yaml
def read_dotconfig():
@@ -352,6 +352,95 @@ class Branch:
else:
print(log.red('failed'))
+ def check_trailers(self, repo, commit, log):
+ def identity(actor):
+ return '%s <%s>' % (actor.name, actor.email)
+
+ def sign_off(actor):
+ return 'Signed-off-by: %s <%s>' % (actor.name, actor.email)
+
+ with repo.config_reader() as config:
+ abbrev = config.get_value('core', 'abbrev', 12)
+
+ signoffs = {
+ 'committer': {
+ 'identity': identity(commit.committer),
+ 'present': False,
+ },
+ 'author': {
+ 'identity': identity(commit.author),
+ 'present': False,
+ },
+ }
+
+ # skip merge commits
+ if len(commit.parents) > 1:
+ return
+
+ committer = identity(commit.committer)
+ author = identity(commit.author)
+
+ proc = repo.git.execute(['git', 'interpret-trailers', '--parse'], as_process = True,
+ istream = subprocess.PIPE)
+ stdout, _ = proc.communicate(str(commit.message).encode())
+
+ for trailer in stdout.decode().splitlines():
+ key, value = map(lambda x: x.strip(), trailer.split(':', 1))
+
+ if key == 'Signed-off-by':
+ if value == committer:
+ signoffs['committer']['present'] = True
+
+ if value == author:
+ signoffs['author']['present'] = True
+
+ hexsha = binascii.hexlify(commit.binsha).decode()[0:abbrev]
+
+ for key, value in signoffs.items():
+ if not value['present']:
+ print('%s: commit %s ("%s") in branch %s' % (log.red('ERROR', Log.STYLE_BOLD),
+ hexsha, commit.summary, self))
+ print('%s is missing a Signed-off-by: from its %s %s' % (' ' * 5, key,
+ value['identity']))
+
+ def check_references(self, repo, commit, log):
+ with repo.config_reader() as config:
+ abbrev = config.get_value('core', 'abbrev', 12)
+
+ proc = repo.git.execute(['git', 'interpret-trailers', '--parse'], as_process = True,
+ istream = subprocess.PIPE)
+ stdout, _ = proc.communicate(str(commit.message).encode())
+ trailers = []
+
+ for trailer in stdout.decode().splitlines():
+ key, value = map(lambda x: x.strip(), trailer.split(':', 1))
+
+ if key == 'Fixes':
+ match = re.match('([0-9a-f]+) \("(.*)"\)', value)
+ ref, subject = match.group(1, 2)
+
+ branches = repo.git.branch('--contains', ref)
+ for branch in branches.splitlines():
+ match = re.match('\*?\W+(.*)', branch)
+ if self.name == match.group(1):
+ break
+ else:
+ hexsha = binascii.hexlify(commit.binsha).decode()[0:abbrev]
+ print('%s: commit %s ("%s") referenced by' % (log.red('ERROR',
+ style = Log.STYLE_BOLD),
+ log.yellow(ref), subject))
+ print('%s commit %s ("%s")' % (' ' * 5, log.yellow(hexsha), commit.summary))
+ print('%s was not found in branch %s' % (' ' * 5, log.green(self)))
+
+ def check(self, repo, log):
+ rev_list = '%s..%s' % (self.branches[0], self)
+
+ print('checking branch %s...' % self)
+
+ for commit in repo.iter_commits(rev_list):
+ self.check_trailers(repo, commit, log)
+ self.check_references(repo, commit, log)
+
def checkout(self, repo, dry_run = False):
print('checking out %s...' % self.name)
@@ -786,6 +875,41 @@ class CommandBuild(Command):
repo = git.Repo('.')
repo.git.worktree('remove', worktree)
+class CommandCheck(Command):
+ name = 'check'
+ help = 'check branches'
+
+ @classmethod
+ def setup(cls, parser):
+ super().setup(parser)
+ parser.add_argument('branches', metavar = 'BRANCH', nargs = '*',
+ help = 'names of branches to check')
+ parser.add_argument('-c', '--color', action = 'store_true',
+ default = True)
+ parser.add_argument('--no-color', action = 'store_false',
+ dest = 'color', help = 'disable log coloring')
+ parser.add_argument('-v', '--verbose', action = 'store_true',
+ help = 'increase verbosity')
+
+ @classmethod
+ def run(cls, args):
+ log = Log(args.color)
+ repo = git.Repo('.')
+ tree = load_tree()
+
+ branches = []
+
+ for build in tree.builds:
+ for branch in build.branches:
+ if args.branches and branch.name not in args.branches:
+ continue
+
+ if branch not in branches:
+ branches.append(branch)
+
+ for branch in branches:
+ branch.check(repo, log)
+
class CommandMerge(Command):
name = 'merge'
help = 'merge branch'
@@ -909,6 +1033,7 @@ class CommandTag(Command):
commands = [
CommandBuild,
+ CommandCheck,
CommandMerge,
CommandPush,
CommandRequestPull,