diff --git a/extras/git.pre-commit b/extras/git.pre-commit new file mode 100755 index 0000000000000000000000000000000000000000..5a627198950f191e4c2c64fe44d23cf170cfcf50 --- /dev/null +++ b/extras/git.pre-commit @@ -0,0 +1,139 @@ +#!/usr/bin/env python + +# Adapted from: http://tech.yipit.com/2011/11/16/183772396/ +# Changes by Wibowo Arindrarto +# Changes: +# - Allow code modification by linters to be comitted +# - Updated CHECKS +# - Python 3 calls + code style updates +# +# Usage: save this file into your .git/hooks directory as `pre-commit` +# and set it to executable + +import os +import re +import subprocess +import sys + +modified = re.compile(r"^[MA]\s+(?P<name>.*)$") + +CHECKS = [ + { + "exe": "scalariform", + "output": "Formatting code with scalariform ...", + # Remove lines without filenames + "command": "scalariform -s=2.11.1 -p=scalariformStyle.properties --quiet %s", + "match_files": [".*scala$"], + "print_filename": False, + "commit_changes": True, + }, +] + + +def matches_file(file_name, match_files): + return any(re.compile(match_file).match(file_name) for match_file + in match_files) + + +def check_files(files, check): + result = 0 + print(check["output"]) + for file_name in files: + + if not "match_files" in check or \ + matches_file(file_name, check["match_files"]): + + if not "ignore_files" in check or \ + not matches_file(file_name, check["ignore_files"]): + + process = subprocess.Popen(check["command"] % file_name, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=True) + + out, err = process.communicate() + if out or err: + if check["print_filename"]: + prefix = "\t%s:" % file_name + else: + prefix = "\t" + output_lines = ["%s%s" % (prefix, line) for + line in out.splitlines()] + print("\n".join(output_lines)) + if err: + print(err) + result = 1 + elif check["commit_changes"]: + p = subprocess.Popen(["git", "add", file_name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p.communicate() + return result + + +def main(all_files): + # Check that the required linters and code checkers are all present + for check in CHECKS: + p = subprocess.Popen(["which", check["exe"]], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = p.communicate() + if not out: + print("Required commit hook executable '%s' not found." % check["exe"]) + sys.exit(1) + + # Stash any changes to the working tree that are not going to be committed + subprocess.call(["git", "stash", "-u", "--keep-index"], stdout=subprocess.PIPE) + + files = [] + if all_files: + for root, dirs, file_names in os.walk("."): + for file_name in file_names: + files.append(os.path.join(root, file_name)) + else: + p = subprocess.Popen(["git", "status", "--porcelain"], + stdout=subprocess.PIPE) + out, err = p.communicate() + for line in out.splitlines(): + match = modified.match(line) + if match: + files.append(match.group("name")) + + result = 0 + for check in CHECKS: + result = check_files(files, check) or result + + # Strategy: + # - Check if the linters made any changes + # - If there are no changes, pop the stash and commit + # - Otherwise: + # - Stash the change + # - Pop stash@{1} + # - Checkout stash@{0} + # - Drop stash@{0} (cannot pop directly since stash may conflict) + # - Commit + # This is because the initial stash will conflict with any possible + # changes made by the linters + p = subprocess.Popen(["git", "status", "--porcelain"], + stdout=subprocess.PIPE) + out, err = p.communicate() + if not out.strip(): + subprocess.call(["git", "stash", "pop"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + subprocess.call(["git", "stash"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.call(["git", "stash", "pop", "--quiet", "--index", "stash@{1}"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.call(["git", "checkout", "stash", "--", "."], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.call(["git", "stash", "drop"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + sys.exit(result) + + +if __name__ == "__main__": + + all_files = False + + if len(sys.argv) > 1 and sys.argv[1] == "--all-files": + all_files = True + + main(all_files)