git.pre-commit 4.62 KB
Newer Older
bow's avatar
bow committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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)