2020-02-16 20:58:01 +01:00
#!/usr/bin/env python
2014-12-22 15:34:55 +01:00
2019-12-12 06:58:51 +01:00
# Future imports for Python 2.7, mandatory in 3.0
from __future__ import division
2015-03-06 11:56:57 +01:00
from __future__ import print_function
2019-12-12 06:58:51 +01:00
from __future__ import unicode_literals
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 " ,
2020-02-18 15:01:56 +01:00
" Directory authority changes " ,
# These aren't preferred, but sortChanges knows how to clean them up.
" Code simplifications and refactoring " ,
" Code simplification and refactorings " ,
" Code simplifications and refactorings " ] )
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
2018-09-18 12:39:03 +02:00
def split_tor_version ( version ) :
'''
Return the initial numeric components of the Tor version as a list of ints .
For versions earlier than 0.1 .0 , returns MAJOR , MINOR , and MICRO .
For versions 0.1 .0 and later , returns MAJOR , MINOR , MICRO , and PATCHLEVEL if present .
If the version is malformed , returns None .
'''
2023-12-09 16:16:08 +01:00
version_match = re . match ( r ' ([0-9]+) \ .([0-9]+) \ .([0-9]+)( \ .([0-9]+))? ' , version )
2018-09-18 12:39:03 +02:00
if version_match is None :
return None
version_groups = version_match . groups ( )
if version_groups is None :
return None
if len ( version_groups ) < 3 :
return None
if len ( version_groups ) != 5 :
return None
version_components = version_groups [ 0 : 3 ]
version_components + = version_groups [ 4 : 5 ]
try :
version_list = [ int ( v ) for v in version_components if v is not None ]
except ValueError :
return None
return version_list
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 ' " )
2023-12-09 16:16:08 +01:00
elif not re . search ( r ' [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 :
2023-12-09 16:16:08 +01:00
bugfix_match = re . search ( r ' bugfix on ([0-9]+ \ .[0-9]+ \ .[0-9]+) ' , contents )
2018-09-14 17:23:02 +02:00
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 ' .) " )
2018-09-18 12:39:03 +02:00
else :
bugfix_match = re . search ( ' bugfix on ([0-9a-z][-.0-9a-z]+[0-9a-z]) ' , contents )
2019-01-17 14:46:06 +01:00
bugfix_group = bugfix_match . groups ( ) if bugfix_match is not None else None
2018-09-18 12:39:03 +02:00
bugfix_version = bugfix_group [ 0 ] if bugfix_group is not None else None
package_version = os . environ . get ( ' PACKAGE_VERSION ' , None )
if bugfix_version is None :
# This should be unreachable, unless the patterns are out of sync
warn ( " Malformed bugfix version. " )
elif package_version is not None :
# If $PACKAGE_VERSION isn't set, skip this check
bugfix_split = split_tor_version ( bugfix_version )
package_split = split_tor_version ( package_version )
if bugfix_split is None :
# This should be unreachable, unless the patterns are out of sync
warn ( " Malformed bugfix version: ' {} ' . " . format ( bugfix_version ) )
elif package_split is None :
# This should be unreachable, unless the patterns are out of sync, or the package versioning scheme has changed
warn ( " Malformed $PACKAGE_VERSION: ' {} ' . " . format ( package_version ) )
elif bugfix_split > package_split :
warn ( " Bugfixes must be made on earlier versions (or this version). (Bugfix on version: ' {} ' , current tor package version: ' {} ' .) " . format ( bugfix_version , package_version ) )
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 )