summaryrefslogtreecommitdiff
path: root/scripts/maint/lintChanges.py
blob: b418065ffbd543946ececcb58b46dea6e4630e06 (plain)
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
#!/usr/bin/python

from __future__ import print_function
from __future__ import with_statement
import sys
import re
import os


KNOWN_GROUPS = set([
    "Minor bugfix",
    "Minor bugfixes",
    "Major bugfix",
    "Major bugfixes",
    "Minor feature",
    "Minor features",
    "Major feature",
    "Major features",
    "New system requirements",
    "Testing",
    "Documentation",
    "Code simplification and refactoring",
    "Removed features",
    "Deprecated features"])

NEEDS_SUBCATEGORIES = set([
    "Minor bugfix",
    "Minor bugfixes",
    "Major bugfix",
    "Major bugfixes",
    "Minor feature",
    "Minor features",
    "Major feature",
    "Major features",
    ])

def lintfile(fname):
    have_warned = []

    def warn(s):
        if not have_warned:
            have_warned.append(1)
            print("{}:".format(fname))
        print("\t{}".format(s))

    m = re.search(r'(\d{3,})', os.path.basename(fname))
    if m:
        bugnum = m.group(1)
    else:
        bugnum = None

    with open(fname) as f:
        contents = f.read()

    if bugnum and bugnum not in contents:
        warn("bug number {} does not appear".format(bugnum))

    m = re.match(r'^[ ]{2}o ([^\(:]*)([^:]*):', contents)
    if not m:
        warn("Header not in format expected. ('  o Foo:' or '  o Foo (Bar):')")
    elif m.group(1).strip() not in KNOWN_GROUPS:
        warn("Unrecognized header: %r" % m.group(1))
    elif (m.group(1) in NEEDS_SUBCATEGORIES and '(' not in m.group(2)):
        warn("Missing subcategory on %r" % m.group(1))

    if m:
        isBug = ("bug" in m.group(1).lower() or "fix" in m.group(1).lower())
    else:
        isBug = False

    contents = " ".join(contents.split())

    if re.search(r'\#\d{2,}', contents):
        warn("Don't use a # before ticket numbers. ('bug 1234' not '#1234')")

    if isBug and not re.search(r'(\d+)', contents):
        warn("Ticket marked as bugfix, but does not mention a number.")
    elif isBug and not re.search(r'Fixes ([a-z ]*)bug (\d+)', contents):
        warn("Ticket marked as bugfix, but does not say 'Fixes bug XXX'")

    if re.search(r'[bB]ug (\d+)', contents):
        if not re.search(r'[Bb]ugfix on ', contents):
            warn("Bugfix does not say 'bugfix on X.Y.Z'")
        elif not re.search('[fF]ixes ([a-z ]*)bug (\d+); bugfix on ',
                           contents):
            warn("Bugfix does not say 'Fixes bug X; bugfix on Y'")
        elif re.search('tor-([0-9]+)', contents):
            warn("Do not prefix versions with 'tor-'. ('0.1.2', not 'tor-0.1.2'.)")

    return have_warned != []

def files(args):
    """Walk through the arguments: for directories, yield their contents;
       for files, just yield the files. Only search one level deep, because
       that's how the changes directory is laid out."""
    for f in args:
        if os.path.isdir(f):
            for item in os.listdir(f):
                if item.startswith("."): #ignore dotfiles
                    continue
                yield os.path.join(f, item)
        else:
            yield f

if __name__ == '__main__':
    problems = 0
    for fname in files(sys.argv[1:]):
        if fname.endswith("~"):
            continue
        if lintfile(fname):
            problems += 1

    if problems:
        sys.exit(1)
    else:
        sys.exit(0)