|
@@ -20,8 +20,19 @@ KNOWN_GROUPS = set([
|
|
|
"Testing",
|
|
|
"Documentation",
|
|
|
"Code simplification and refactoring",
|
|
|
- "Removed features"])
|
|
|
+ "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 = []
|
|
@@ -46,13 +57,11 @@ def lintfile(fname):
|
|
|
|
|
|
m = re.match(r'^[ ]{2}o ([^\(:]*)([^:]*):', contents)
|
|
|
if not m:
|
|
|
- warn("header not in format expected")
|
|
|
+ warn("Header not in format expected. (' o Foo:' or ' o Foo (Bar):')")
|
|
|
elif m.group(1).strip() not in KNOWN_GROUPS:
|
|
|
- warn("Weird header: %r" % m.group(1))
|
|
|
- elif (("bugfix" in m.group(1) or "feature" in m.group(1)) and
|
|
|
- ("Removed" not in m.group(1)) and
|
|
|
- '(' not in m.group(2)):
|
|
|
- warn("Missing subcategory on %s" % m.group(1))
|
|
|
+ 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())
|
|
@@ -62,25 +71,46 @@ def lintfile(fname):
|
|
|
contents = " ".join(contents.split())
|
|
|
|
|
|
if re.search(r'\#\d{2,}', contents):
|
|
|
- warn("don't use a # before ticket numbers")
|
|
|
+ warn("Don't use a # before ticket numbers. ('bug 1234' not '#1234')")
|
|
|
|
|
|
if isBug and not re.search(r'(\d+)', contents):
|
|
|
- warn("bugfix does not mention a number")
|
|
|
+ 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("bugfix does not say 'Fixes bug XXX'")
|
|
|
+ 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'")
|
|
|
+ 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 incant is not semicoloned")
|
|
|
+ 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-'")
|
|
|
-
|
|
|
+ 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("."):
|
|
|
+ continue
|
|
|
+ yield os.path.join(f, item)
|
|
|
+ else:
|
|
|
+ yield f
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
- for fname in sys.argv[1:]:
|
|
|
+ problems = 0
|
|
|
+ for fname in files(sys.argv[1:]):
|
|
|
if fname.endswith("~"):
|
|
|
continue
|
|
|
- lintfile(fname)
|
|
|
+ if lintfile(fname):
|
|
|
+ problems += 1
|
|
|
+
|
|
|
+ if problems:
|
|
|
+ sys.exit(1)
|
|
|
+ else:
|
|
|
+ sys.exit(0)
|