11import json
22import requests
33import os
4+ import sys
5+ import argparse
46import shutil
57import sh
68from sh .contrib import git
1517apply_errors = []
1618stats = []
1719
20+ """
21+ Setup the command line argument parsing object.
22+ """
23+ cli_parser = argparse .ArgumentParser (description = "Apply patches to any common file(s) in"
24+ " all Adafruit CircuitPython Libraries." )
25+ cli_parser .add_argument ("-l" , "--list" , help = "Lists the available patches to run." ,
26+ action = 'store_true' )
27+ cli_parser .add_argument ("-p" , help = "Runs only the single patch referenced." ,
28+ metavar = "<PATCH FILENAME>" , dest = "patch" )
29+ cli_parser .add_argument ("-f" , help = "Adds the referenced FLAGS to the git.am call."
30+ " Only available when using '-p'. Enclose flags in brackets '[]'."
31+ " Multiple flags can be passed. NOTE: '--signoff' is already used "
32+ " used by default, and will be ignored. EXAMPLE: -f [-C0] -f [-s]" ,
33+ metavar = "FLAGS" , action = "append" , dest = "flags" , type = str )
34+ cli_parser .add_argument ("--use-apply" , help = "Forces use of 'git apply' instead of 'git am'."
35+ " This is necessary when needing to use 'apply' flags not available"
36+ " to 'am' (e.g. '--unidiff-zero'). Only available when using '-p'." ,
37+ action = "store_true" , dest = "use_apply" )
38+
1839def get_repo_list ():
1940 """ Uses adabot.circuitpython_libraries module to get a list of
2041 CircuitPython repositories. Filters the list down to adafruit
@@ -36,48 +57,77 @@ def get_patches():
3657 """
3758 return_list = []
3859 contents = requests .get ("https://api.github.com/repos/adafruit/adabot/contents/patches" )
39-
4060 if contents .ok :
4161 for patch in contents .json ():
4262 patch_name = patch ["name" ]
4363 return_list .append (patch_name )
4464
4565 return return_list
4666
47- def apply_patch (repo_directory , patch_filepath , repo , patch ):
67+ def apply_patch (repo_directory , patch_filepath , repo , patch , flags , use_apply ):
4868 """ Apply the `patch` in `patch_filepath` to the `repo` in
49- `repo_directory` using git am. --signoff will sign the commit
69+ `repo_directory` using git am or git apply. The commit
5070 with the user running the script (adabot if credentials are set
5171 for that).
72+
73+ When `use_apply` is true, the `--apply` flag is automatically added
74+ to ensure that any passed flags that turn off apply (e.g. `--check`)
75+ are overridden.
5276 """
5377 if not os .getcwd () == repo_directory :
5478 os .chdir (repo_directory )
5579
56- try :
57- git .am ("--signoff" , patch_filepath )
58- except sh .ErrorReturnCode as Err :
59- apply_errors .append (dict (repo_name = repo ,
60- patch_name = patch , error = Err .stderr ))
61- return False
80+ if not use_apply :
81+ try :
82+ git .am (flags , patch_filepath )
83+ except sh .ErrorReturnCode as Err :
84+ apply_errors .append (dict (repo_name = repo ,
85+ patch_name = patch , error = Err .stderr ))
86+ return False
87+ else :
88+ apply_flags = ["--apply" ]
89+ for flag in flags :
90+ if not flag == "--signoff" :
91+ apply_flags .append (flag )
92+ try :
93+ git .apply (apply_flags , patch_filepath )
94+ except sh .ErrorReturnCode as Err :
95+ apply_errors .append (dict (repo_name = repo ,
96+ patch_name = patch , error = Err .stderr ))
97+ return False
98+
99+ with open (patch_filepath ) as f :
100+ for line in f :
101+ if "[PATCH]" in line :
102+ message = '"' + line [(line .find ("]" ) + 2 ):] + '"'
103+ break
104+ try :
105+ git .commit ("-a" , "-m" , message )
106+ except sh .ErrorReturnCode as Err :
107+ apply_errors .append (dict (repo_name = repo ,
108+ patch_name = patch , error = Err .stderr ))
109+ return False
62110
63111 try :
64112 git .push ()
65113 except sh .ErrorReturnCode as Err :
66114 apply_errors .append (dict (repo_name = repo ,
67115 patch_name = patch , error = Err .stderr ))
68116 return False
69-
70117 return True
71118
72- def check_patches (repo ):
119+ def check_patches (repo , patches , flags , use_apply ):
73120 """ Gather a list of patches from the `adabot/patches` directory
74121 on the adabot repo. Clone the `repo` and run git apply --check
75122 to test wether it requires any of the gathered patches.
123+
124+ When `use_apply` is true, any flags except `--apply` are passed
125+ through to the check call. This ensures that the check call is
126+ representative of the actual apply call.
76127 """
77128 applied = 0
78129 skipped = 0
79130 failed = 0
80- patches = get_patches ()
81131
82132 repo_directory = lib_directory + repo ["name" ]
83133
@@ -100,19 +150,31 @@ def check_patches(repo):
100150 patch_filepath = patch_directory + patch
101151
102152 try :
103- git .apply ("--check" , patch_filepath )
153+ check_flags = ["--check" ]
154+ if use_apply :
155+ for flag in flags :
156+ if not flag in ("--apply" , "--signoff" ):
157+ check_flags .append (flag )
158+ git .apply (check_flags , patch_filepath )
104159 run_apply = True
105- except sh .ErrorReturnCode_1 :
160+ except sh .ErrorReturnCode_1 as Err :
106161 run_apply = False
107- skipped += 1
108- except sh .ErrorReturnCode_128 as Err :
162+ if not b"error" in Err .stderr :
163+ skipped += 1
164+ else :
165+ failed += 1
166+ check_errors .append (dict (repo_name = repo ["name" ],
167+ patch_name = patch , error = Err .stderr ))
168+
169+ except sh .ErrorReturnCode as Err :
109170 run_apply = False
110171 failed += 1
111172 check_errors .append (dict (repo_name = repo ["name" ],
112173 patch_name = patch , error = Err .stderr ))
113174
114175 if run_apply :
115- result = apply_patch (repo_directory , patch_filepath , repo ["name" ], patch )
176+ result = apply_patch (repo_directory , patch_filepath , repo ["name" ],
177+ patch , flags , use_apply )
116178 if result :
117179 applied += 1
118180 else :
@@ -121,6 +183,31 @@ def check_patches(repo):
121183 return [applied , skipped , failed ]
122184
123185if __name__ == "__main__" :
186+
187+ run_patches = get_patches ()
188+ flags = ["--signoff" ]
189+
190+ cli_args = cli_parser .parse_args ()
191+ if cli_args .list :
192+ print ("Available Patches:" , run_patches )
193+ sys .exit ()
194+ if cli_args .patch :
195+ if not cli_args .patch in run_patches :
196+ raise ValueError ("'{}' is not an available patchfile." .format (cli_args .patch ))
197+ run_patches = [cli_args .patch ]
198+ if not cli_args .flags == None :
199+ if not cli_args .patch :
200+ raise RuntimeError ("Must be used with a single patch. See help (-h) for usage." )
201+ if "[-i]" in cli_args .flags :
202+ raise ValueError ("Interactive Mode flag not allowed." )
203+ for flag in cli_args .flags :
204+ if not flag == "[--signoff]" :
205+ flags .append (flag .strip ("[]" ))
206+ if cli_args .use_apply :
207+ if not cli_args .patch :
208+ raise RuntimeError ("Must be used with a single patch. See help (-h) for usage." )
209+ use_apply = cli_args .use_apply
210+
124211 print (".... Beginning Patch Updates ...." )
125212 print (".... Working directory:" , working_directory )
126213 print (".... Library directory:" , lib_directory )
@@ -136,23 +223,23 @@ def check_patches(repo):
136223 for lib in libs :
137224 shutil .rmtree (lib_directory + lib )
138225 except FileNotFoundError :
139- pass
226+ pass
140227
141228 repos = get_repo_list ()
142229 print (".... Running Patch Checks On" , len (repos ), "Repos ...." )
143230
144231 for repo in repos :
145- results = check_patches (repo )
146- for k in range (len ( stats ) ):
147- stats [k ] += results [k ]
232+ results = check_patches (repo , run_patches , flags , use_apply )
233+ for k in range (3 ):
234+ stats [k ] += results [k ]
148235
149236 print (".... Patch Updates Completed ...." )
150237 print (".... Patches Applied:" , stats [0 ])
151238 print (".... Patches Skipped:" , stats [1 ])
152239 print (".... Patches Failed:" , stats [2 ], "\n " )
153240 print (".... Patch Check Failure Report ...." )
154241 if len (check_errors ) > 0 :
155- for error , _ in check_errors :
242+ for error in check_errors :
156243 print (">>" , error )
157244 else :
158245 print ("No Failures" )
@@ -163,4 +250,4 @@ def check_patches(repo):
163250 print (">>" , error )
164251 else :
165252 print ("No Failures" )
166-
253+
0 commit comments