summaryrefslogtreecommitdiff
path: root/check-style.py
diff options
context:
space:
mode:
authorCarlos Garnacho <carlosg@gnome.org>2021-06-13 20:25:19 +0200
committerCarlos Garnacho <carlosg@gnome.org>2021-06-17 00:58:11 +0200
commitf613557b5e5186cfa0ef451e5b4d7e57782cba69 (patch)
tree1c52a8f7ce859d94ddb3e44b56e7d03f9b82489e /check-style.py
parent40a49da8856dd0325a5407829d064247bf9b40e0 (diff)
downloadtracker-f613557b5e5186cfa0ef451e5b4d7e57782cba69.tar.gz
build: Add check-style.py helper script
This script uses uncrustify to detect code style breaks in/around the given SHA diff. There's two ways to use it: - `check-style.py --sha $SHA` prints in stdout the diffs with the changes suggested to the modifications done between HEAD and $SHA. The default with no --sha specified is HEAD^ This is meant for integration in CI, in order to show code style differences, and suggest a set of changes. - `check-style.py -r` in addition amends the changes in the last commit. This is ideal to rewrite all commits in a branch one by one with the suggested changes, e.g.: `$ git rebase origin/master --exec "./check-style -r"` One caveat is that there is per-function granularity in the suggested code changes, so the script might suggest to fix style in unmodified portions of the changed functions.
Diffstat (limited to 'check-style.py')
-rwxr-xr-xcheck-style.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/check-style.py b/check-style.py
new file mode 100755
index 000000000..df2ae4651
--- /dev/null
+++ b/check-style.py
@@ -0,0 +1,131 @@
+#!/bin/env python3
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+def run_diff(sha):
+ proc = subprocess.Popen(["git", "diff", "--function-context", sha, "HEAD"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ files = proc.stdout.read().strip().decode('utf-8')
+ return files.split('\n')
+
+def find_chunks(diff):
+ file_entry_re = re.compile('^\+\+\+ b/(.*)$')
+ diff_chunk_re = re.compile('^@@ -\d+,\d+ \+(\d+),(\d+)')
+ file = None
+ chunks = []
+
+ for line in diff:
+ match = file_entry_re.match(line)
+ if match:
+ file = match.group(1)
+
+ match = diff_chunk_re.match(line)
+ if match:
+ start = int(match.group(1))
+ len = int(match.group(2))
+ end = start + len
+
+ if file.endswith('.c') or file.endswith('.h') or file.endswith('.vala'):
+ chunks.append({ 'file': file, 'start': start, 'end': end })
+
+ return chunks
+
+def reformat_chunks(chunks, rewrite):
+ # Creates temp file with INDENT-ON/OFF comments
+ def create_temp_file(file, start, end):
+ with open(file) as f:
+ tmp = tempfile.NamedTemporaryFile()
+ tmp.write(b'/** *INDENT-OFF* **/\n')
+ for i, line in enumerate(f):
+ if i == start - 2:
+ tmp.write(b'/** *INDENT-ON* **/\n')
+
+ tmp.write(bytes(line, 'utf-8'))
+
+ if i == end - 1:
+ tmp.write(b'/** *INDENT-OFF* **/\n')
+
+ tmp.seek(0)
+
+ return tmp
+
+ # Removes uncrustify INDENT-ON/OFF helper comments
+ def remove_indent_comments(output):
+ tmp = tempfile.NamedTemporaryFile()
+
+ for line in output:
+ if line != b'/** *INDENT-OFF* **/\n' and line != b'/** *INDENT-ON* **/\n':
+ tmp.write(line)
+
+ tmp.seek(0)
+
+ return tmp
+
+ changed = None
+
+ for chunk in chunks:
+ # Add INDENT-ON/OFF comments
+ tmp = create_temp_file(chunk['file'], chunk['start'], chunk['end'])
+
+ # uncrustify chunk
+ proc = subprocess.Popen(["uncrustify", "-c", "./utils/uncrustify.cfg", "-f", tmp.name], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
+ reindented = proc.stdout.readlines()
+ tmp.close()
+
+ # Remove INDENT-ON/OFF comments
+ formatted = remove_indent_comments(reindented)
+
+ if dry_run is True:
+ # Show changes
+ proc = subprocess.Popen(["diff", "-up", "--color=always", chunk['file'], formatted.name], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ diff = proc.stdout.read().decode('utf-8')
+ if diff != '':
+ output = re.sub('\t', '↦\t', diff)
+ print(output)
+ changed = True
+ else:
+ # Apply changes
+ diff = subprocess.Popen(["diff", "-up", chunk['file'], formatted.name], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ patch = subprocess.Popen(["patch", chunk['file']], stdin=diff.stdout)
+ diff.stdout.close()
+ patch.communicate()
+
+ formatted.close()
+
+ return changed
+
+
+parser = argparse.ArgumentParser(description='Check code style.')
+parser.add_argument('--sha', metavar='SHA', type=str,
+ help='SHA for the commit to compare HEAD with')
+parser.add_argument('--dry-run', '-d', type=bool,
+ action=argparse.BooleanOptionalAction,
+ help='Only print changes to stdout, do not change code')
+parser.add_argument('--rewrite', '-r', type=bool,
+ action=argparse.BooleanOptionalAction,
+ help='Whether to amend the result to the last commit (e.g. \'git rebase --exec "%(prog)s -r"\')')
+
+# Change CWD to script location, necessary for always locating the configuration file
+os.chdir(os.path.dirname(os.path.abspath(sys.argv[0])))
+
+args = parser.parse_args()
+sha = args.sha or 'HEAD^'
+rewrite = args.rewrite
+dry_run = args.dry_run
+
+diff = run_diff(sha)
+chunks = find_chunks(diff)
+changed = reformat_chunks(chunks, rewrite)
+
+if dry_run is not True and rewrite is True:
+ proc = subprocess.Popen(["git", "commit", "--all", "--amend", "-C", "HEAD"], stdout=subprocess.DEVNULL)
+ os._exit(0)
+elif dry_run is True and changed is True:
+ print ("\nIssue the following command in your local tree to apply the suggested changes (needs uncrustify installed):\n\n $ git rebase origin/master --exec \"./check-style.py -r\" \n")
+ os._exit(-1)
+
+os._exit(0)