2014-12-22 15:34:55 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
|
2015-03-06 11:56:57 +01:00
|
|
|
from __future__ import print_function
|
|
|
|
from __future__ import with_statement
|
2014-12-22 15:34:55 +01:00
|
|
|
import sys
|
|
|
|
import re
|
2015-03-06 11:56:57 +01:00
|
|
|
import os
|
2014-12-22 15:34:55 +01:00
|
|
|
|
|
|
|
|
2016-12-19 08:37:32 +01:00
|
|
|
KNOWN_GROUPS = set([
|
2016-02-01 22:46:29 +01:00
|
|
|
"Minor bugfix",
|
|
|
|
"Minor bugfixes",
|
|
|
|
"Major bugfix",
|
|
|
|
"Major bugfixes",
|
|
|
|
"Minor feature",
|
|
|
|
"Minor features",
|
|
|
|
"Major feature",
|
|
|
|
"Major features",
|
2016-03-21 16:14:57 +01:00
|
|
|
"New system requirements",
|
2016-02-01 22:46:29 +01:00
|
|
|
"Testing",
|
|
|
|
"Documentation",
|
|
|
|
"Code simplification and refactoring",
|
2017-09-19 16:10:38 +02:00
|
|
|
"Removed features",
|
2017-10-17 19:58:21 +02:00
|
|
|
"Deprecated features",
|
|
|
|
"Directory authority changes"])
|
2016-02-01 22:46:29 +01:00
|
|
|
|
2017-09-19 16:10:38 +02:00
|
|
|
NEEDS_SUBCATEGORIES = set([
|
|
|
|
"Minor bugfix",
|
|
|
|
"Minor bugfixes",
|
|
|
|
"Major bugfix",
|
|
|
|
"Major bugfixes",
|
|
|
|
"Minor feature",
|
|
|
|
"Minor features",
|
|
|
|
"Major feature",
|
|
|
|
"Major features",
|
|
|
|
])
|
2016-12-19 08:37:32 +01:00
|
|
|
|
2014-12-22 15:34:55 +01:00
|
|
|
def lintfile(fname):
|
|
|
|
have_warned = []
|
2015-03-06 11:56:57 +01:00
|
|
|
|
2014-12-22 15:34:55 +01:00
|
|
|
def warn(s):
|
|
|
|
if not have_warned:
|
|
|
|
have_warned.append(1)
|
2015-03-06 11:56:57 +01:00
|
|
|
print("{}:".format(fname))
|
|
|
|
print("\t{}".format(s))
|
2014-12-22 15:34:55 +01:00
|
|
|
|
2015-03-06 11:56:57 +01:00
|
|
|
m = re.search(r'(\d{3,})', os.path.basename(fname))
|
2014-12-22 15:34:55 +01:00
|
|
|
if m:
|
|
|
|
bugnum = m.group(1)
|
|
|
|
else:
|
|
|
|
bugnum = None
|
|
|
|
|
|
|
|
with open(fname) as f:
|
|
|
|
contents = f.read()
|
|
|
|
|
|
|
|
if bugnum and bugnum not in contents:
|
2015-03-06 11:56:57 +01:00
|
|
|
warn("bug number {} does not appear".format(bugnum))
|
2014-12-22 15:34:55 +01:00
|
|
|
|
2016-02-01 22:46:29 +01:00
|
|
|
m = re.match(r'^[ ]{2}o ([^\(:]*)([^:]*):', contents)
|
|
|
|
if not m:
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Header not in format expected. (' o Foo:' or ' o Foo (Bar):')")
|
2016-02-01 22:46:29 +01:00
|
|
|
elif m.group(1).strip() not in KNOWN_GROUPS:
|
2017-09-19 16:10:38 +02:00
|
|
|
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))
|
2016-02-01 22:46:29 +01:00
|
|
|
|
2016-09-22 14:33:09 +02:00
|
|
|
if m:
|
|
|
|
isBug = ("bug" in m.group(1).lower() or "fix" in m.group(1).lower())
|
|
|
|
else:
|
|
|
|
isBug = False
|
2014-12-22 16:00:34 +01:00
|
|
|
|
2014-12-22 15:34:55 +01:00
|
|
|
contents = " ".join(contents.split())
|
|
|
|
|
2015-02-19 15:54:09 +01:00
|
|
|
if re.search(r'\#\d{2,}', contents):
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Don't use a # before ticket numbers. ('bug 1234' not '#1234')")
|
2015-02-19 15:54:09 +01:00
|
|
|
|
2014-12-22 15:34:55 +01:00
|
|
|
if isBug and not re.search(r'(\d+)', contents):
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Ticket marked as bugfix, but does not mention a number.")
|
2017-11-05 20:41:39 +01:00
|
|
|
elif isBug and not re.search(r'Fixes ([a-z ]*)bugs? (\d+)', contents):
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Ticket marked as bugfix, but does not say 'Fixes bug XXX'")
|
2014-12-22 15:34:55 +01:00
|
|
|
|
2014-12-22 15:49:33 +01:00
|
|
|
if re.search(r'[bB]ug (\d+)', contents):
|
|
|
|
if not re.search(r'[Bb]ugfix on ', contents):
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Bugfix does not say 'bugfix on X.Y.Z'")
|
2017-11-05 20:41:39 +01:00
|
|
|
elif not re.search('[fF]ixes ([a-z ]*)bugs? (\d+)((, \d+)* and \d+)?; bugfix on ',
|
2015-03-06 11:56:57 +01:00
|
|
|
contents):
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Bugfix does not say 'Fixes bug X; bugfix on Y'")
|
2016-12-19 09:00:56 +01:00
|
|
|
elif re.search('tor-([0-9]+)', contents):
|
2017-09-19 16:10:38 +02:00
|
|
|
warn("Do not prefix versions with 'tor-'. ('0.1.2', not 'tor-0.1.2'.)")
|
2018-09-14 17:23:02 +02:00
|
|
|
else:
|
|
|
|
bugfix_match = re.search('bugfix on ([0-9]+\.[0-9]+\.[0-9]+)', contents)
|
|
|
|
if bugfix_match is None:
|
|
|
|
warn("Versions must have at least 3 digits. ('0.1.2', '0.3.4.8', or '0.3.5.1-alpha'.)")
|
|
|
|
elif bugfix_match.group(0) is None:
|
|
|
|
warn("Versions must have at least 3 digits. ('0.1.2', '0.3.4.8', or '0.3.5.1-alpha'.)")
|
2017-09-19 16:10:38 +02:00
|
|
|
|
|
|
|
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
|
2014-12-22 15:34:55 +01:00
|
|
|
|
2015-03-06 11:56:57 +01:00
|
|
|
if __name__ == '__main__':
|
2017-09-19 16:10:38 +02:00
|
|
|
problems = 0
|
|
|
|
for fname in files(sys.argv[1:]):
|
2014-12-22 15:34:55 +01:00
|
|
|
if fname.endswith("~"):
|
|
|
|
continue
|
2017-09-19 16:10:38 +02:00
|
|
|
if lintfile(fname):
|
|
|
|
problems += 1
|
|
|
|
|
|
|
|
if problems:
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
sys.exit(0)
|