|  | @@ -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("."): #ignore dotfiles
 | 
	
		
			
				|  |  | +                    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)
 |