1
1
#!/usr/bin/env python3
2
2
"""Check proposed changes for common issues."""
3
+
3
4
import sys
4
5
import os .path
5
6
import subprocess
6
7
import sysconfig
8
+ from typing import Callable , Optional
7
9
8
10
9
- def get_python_source_dir ():
10
- src_dir = sysconfig .get_config_var (' abs_srcdir' )
11
+ def get_python_source_dir () -> str :
12
+ src_dir = sysconfig .get_config_var (" abs_srcdir" )
11
13
if not src_dir :
12
- src_dir = sysconfig .get_config_var (' srcdir' )
14
+ src_dir = sysconfig .get_config_var (" srcdir" )
13
15
return os .path .abspath (src_dir )
14
16
15
17
16
18
SRCDIR = get_python_source_dir ()
17
19
18
20
19
- def n_files_str (count ) :
21
+ def n_files_str (count : int ) -> str :
20
22
"""Return 'N file(s)' with the proper plurality on 'file'."""
21
23
s = "s" if count != 1 else ""
22
24
return f"{ count } file{ s } "
23
25
24
26
25
- def status (message , modal = False , info = None ):
27
+ def status (message : str , modal : bool = False , info : Optional [ Callable ] = None ):
26
28
"""Decorator to output status info to stdout."""
29
+
27
30
def decorated_fxn (fxn ):
28
31
def call_fxn (* args , ** kwargs ):
29
- sys .stdout .write (message + ' ... ' )
32
+ sys .stdout .write (message + " ... " )
30
33
sys .stdout .flush ()
31
34
result = fxn (* args , ** kwargs )
32
35
if not modal and not info :
@@ -36,23 +39,24 @@ def call_fxn(*args, **kwargs):
36
39
else :
37
40
print ("yes" if result else "NO" )
38
41
return result
42
+
39
43
return call_fxn
44
+
40
45
return decorated_fxn
41
46
42
47
43
- def get_git_branch ():
48
+ def get_git_branch () -> Optional [ str ] :
44
49
"""Get the symbolic name for the current git branch"""
45
50
cmd = "git rev-parse --abbrev-ref HEAD" .split ()
46
51
try :
47
- return subprocess .check_output (cmd ,
48
- stderr = subprocess .DEVNULL ,
49
- cwd = SRCDIR ,
50
- encoding = 'UTF-8' )
52
+ return subprocess .check_output (
53
+ cmd , stderr = subprocess .DEVNULL , cwd = SRCDIR , encoding = "UTF-8"
54
+ )
51
55
except subprocess .CalledProcessError :
52
56
return None
53
57
54
58
55
- def get_git_upstream_remote ():
59
+ def get_git_upstream_remote () -> str :
56
60
"""
57
61
Get the remote name to use for upstream branches
58
62
@@ -63,14 +67,12 @@ def get_git_upstream_remote():
63
67
"""
64
68
cmd = "git remote -v" .split ()
65
69
output = subprocess .check_output (
66
- cmd ,
67
- stderr = subprocess .DEVNULL ,
68
- cwd = SRCDIR ,
69
- encoding = "UTF-8"
70
+ cmd , stderr = subprocess .DEVNULL , cwd = SRCDIR , encoding = "UTF-8"
70
71
)
71
72
# Filter to desired remotes, accounting for potential uppercasing
72
73
filtered_remotes = {
73
- remote .split ("\t " )[0 ].lower () for remote in output .split ('\n ' )
74
+ remote .split ("\t " )[0 ].lower ()
75
+ for remote in output .split ("\n " )
74
76
if "python/cpython" in remote .lower () and remote .endswith ("(fetch)" )
75
77
}
76
78
if len (filtered_remotes ) == 1 :
@@ -80,7 +82,7 @@ def get_git_upstream_remote():
80
82
if remote_name in filtered_remotes :
81
83
return remote_name
82
84
remotes_found = "\n " .join (
83
- {remote for remote in output .split (' \n ' ) if remote .endswith ("(fetch)" )}
85
+ {remote for remote in output .split (" \n " ) if remote .endswith ("(fetch)" )}
84
86
)
85
87
raise ValueError (
86
88
f"Patchcheck was unable to find an unambiguous upstream remote, "
@@ -89,23 +91,25 @@ def get_git_upstream_remote():
89
91
f"https://devguide.python.org/getting-started/"
90
92
f"git-boot-camp/#cloning-a-forked-cpython-repository "
91
93
f"\n Remotes found: \n { remotes_found } "
92
- )
94
+ )
93
95
94
96
95
- def get_git_remote_default_branch (remote_name ) :
97
+ def get_git_remote_default_branch (remote_name : str ) -> Optional [ str ] :
96
98
"""Get the name of the default branch for the given remote
97
99
98
100
It is typically called 'main', but may differ
99
101
"""
100
102
cmd = f"git remote show { remote_name } " .split ()
101
103
env = os .environ .copy ()
102
- env [' LANG' ] = 'C'
104
+ env [" LANG" ] = "C"
103
105
try :
104
- remote_info = subprocess .check_output (cmd ,
105
- stderr = subprocess .DEVNULL ,
106
- cwd = SRCDIR ,
107
- encoding = 'UTF-8' ,
108
- env = env )
106
+ remote_info = subprocess .check_output (
107
+ cmd ,
108
+ stderr = subprocess .DEVNULL ,
109
+ cwd = SRCDIR ,
110
+ encoding = "UTF-8" ,
111
+ env = env ,
112
+ )
109
113
except subprocess .CalledProcessError :
110
114
return None
111
115
for line in remote_info .splitlines ():
@@ -115,15 +119,17 @@ def get_git_remote_default_branch(remote_name):
115
119
return None
116
120
117
121
118
- @status ("Getting base branch for PR" ,
119
- info = lambda x : x if x is not None else "not a PR branch" )
120
- def get_base_branch ():
121
- if not os .path .exists (os .path .join (SRCDIR , '.git' )):
122
+ @status (
123
+ "Getting base branch for PR" ,
124
+ info = lambda x : x if x is not None else "not a PR branch" ,
125
+ )
126
+ def get_base_branch () -> Optional [str ]:
127
+ if not os .path .exists (os .path .join (SRCDIR , ".git" )):
122
128
# Not a git checkout, so there's no base branch
123
129
return None
124
130
upstream_remote = get_git_upstream_remote ()
125
131
version = sys .version_info
126
- if version .releaselevel == ' alpha' :
132
+ if version .releaselevel == " alpha" :
127
133
base_branch = get_git_remote_default_branch (upstream_remote )
128
134
else :
129
135
base_branch = "{0.major}.{0.minor}" .format (version )
@@ -134,38 +140,40 @@ def get_base_branch():
134
140
return upstream_remote + "/" + base_branch
135
141
136
142
137
- @status ("Getting the list of files that have been added/changed" ,
138
- info = lambda x : n_files_str (len (x )))
139
- def changed_files (base_branch = None ):
143
+ @status (
144
+ "Getting the list of files that have been added/changed" ,
145
+ info = lambda x : n_files_str (len (x )),
146
+ )
147
+ def changed_files (base_branch : Optional [str ] = None ) -> list [str ]:
140
148
"""Get the list of changed or added files from git."""
141
- if os .path .exists (os .path .join (SRCDIR , ' .git' )):
149
+ if os .path .exists (os .path .join (SRCDIR , " .git" )):
142
150
# We just use an existence check here as:
143
151
# directory = normal git checkout/clone
144
152
# file = git worktree directory
145
153
if base_branch :
146
- cmd = ' git diff --name-status ' + base_branch
154
+ cmd = " git diff --name-status " + base_branch
147
155
else :
148
- cmd = ' git status --porcelain'
156
+ cmd = " git status --porcelain"
149
157
filenames = []
150
- with subprocess .Popen (cmd . split (),
151
- stdout = subprocess .PIPE ,
152
- cwd = SRCDIR ) as st :
158
+ with subprocess .Popen (
159
+ cmd . split (), stdout = subprocess .PIPE , cwd = SRCDIR
160
+ ) as st :
153
161
git_file_status , _ = st .communicate ()
154
162
if st .returncode != 0 :
155
- sys .exit (f' error running { cmd } ' )
163
+ sys .exit (f" error running { cmd } " )
156
164
for line in git_file_status .splitlines ():
157
165
line = line .decode ().rstrip ()
158
166
status_text , filename = line .split (maxsplit = 1 )
159
167
status = set (status_text )
160
168
# modified, added or unmerged files
161
- if not status .intersection (' MAU' ):
169
+ if not status .intersection (" MAU" ):
162
170
continue
163
- if ' -> ' in filename :
171
+ if " -> " in filename :
164
172
# file is renamed
165
- filename = filename .split (' -> ' , 2 )[1 ].strip ()
173
+ filename = filename .split (" -> " , 2 )[1 ].strip ()
166
174
filenames .append (filename )
167
175
else :
168
- sys .exit (' need a git checkout to get modified files' )
176
+ sys .exit (" need a git checkout to get modified files" )
169
177
170
178
return list (map (os .path .normpath , filenames ))
171
179
@@ -179,40 +187,45 @@ def docs_modified(file_paths):
179
187
@status ("Misc/ACKS updated" , modal = True )
180
188
def credit_given (file_paths ):
181
189
"""Check if Misc/ACKS has been changed."""
182
- return os .path .join (' Misc' , ' ACKS' ) in file_paths
190
+ return os .path .join (" Misc" , " ACKS" ) in file_paths
183
191
184
192
185
193
@status ("Misc/NEWS.d updated with `blurb`" , modal = True )
186
194
def reported_news (file_paths ):
187
195
"""Check if Misc/NEWS.d has been changed."""
188
- return any (p .startswith (os .path .join ('Misc' , 'NEWS.d' , 'next' ))
189
- for p in file_paths )
196
+ return any (
197
+ p .startswith (os .path .join ("Misc" , "NEWS.d" , "next" ))
198
+ for p in file_paths
199
+ )
190
200
191
201
192
202
@status ("configure regenerated" , modal = True , info = str )
193
203
def regenerated_configure (file_paths ):
194
204
"""Check if configure has been regenerated."""
195
- if ' configure.ac' in file_paths :
196
- return "yes" if ' configure' in file_paths else "no"
205
+ if " configure.ac" in file_paths :
206
+ return "yes" if " configure" in file_paths else "no"
197
207
else :
198
208
return "not needed"
199
209
200
210
201
211
@status ("pyconfig.h.in regenerated" , modal = True , info = str )
202
212
def regenerated_pyconfig_h_in (file_paths ):
203
213
"""Check if pyconfig.h.in has been regenerated."""
204
- if ' configure.ac' in file_paths :
205
- return "yes" if ' pyconfig.h.in' in file_paths else "no"
214
+ if " configure.ac" in file_paths :
215
+ return "yes" if " pyconfig.h.in" in file_paths else "no"
206
216
else :
207
217
return "not needed"
208
218
209
219
210
- def main ():
220
+ def main () -> None :
211
221
base_branch = get_base_branch ()
212
222
file_paths = changed_files (base_branch )
213
- has_doc_files = any (fn for fn in file_paths if fn .startswith ('Doc' ) and
214
- fn .endswith (('.rst' , '.inc' )))
215
- misc_files = {p for p in file_paths if p .startswith ('Misc' )}
223
+ has_doc_files = any (
224
+ fn
225
+ for fn in file_paths
226
+ if fn .startswith ("Doc" ) and fn .endswith ((".rst" , ".inc" ))
227
+ )
228
+ misc_files = {p for p in file_paths if p .startswith ("Misc" )}
216
229
# Docs updated.
217
230
docs_modified (has_doc_files )
218
231
# Misc/ACKS changed.
@@ -225,14 +238,14 @@ def main():
225
238
regenerated_pyconfig_h_in (file_paths )
226
239
227
240
# Test suite run and passed.
228
- has_c_files = any (fn for fn in file_paths if fn .endswith (('.c' , '.h' )))
229
- has_python_files = any (fn for fn in file_paths if fn .endswith (' .py' ))
241
+ has_c_files = any (fn for fn in file_paths if fn .endswith ((".c" , ".h" )))
242
+ has_python_files = any (fn for fn in file_paths if fn .endswith (" .py" ))
230
243
print ()
231
244
if has_c_files :
232
245
print ("Did you run the test suite and check for refleaks?" )
233
246
elif has_python_files :
234
247
print ("Did you run the test suite?" )
235
248
236
249
237
- if __name__ == ' __main__' :
250
+ if __name__ == " __main__" :
238
251
main ()
0 commit comments