33import shlex
44import sys
55import textwrap
6+ from dataclasses import dataclass
67from datetime import datetime , timedelta , timezone
78from importlib .machinery import SourceFileLoader
89from importlib .util import spec_from_loader , module_from_spec
10+ from itertools import groupby
911from os import chdir , getcwd , mkdir , environ
1012from os .path import basename , dirname , realpath
1113from platform import python_version
1214from shutil import rmtree
1315from subprocess import Popen , check_output , CalledProcessError , STDOUT , PIPE
14- from typing import Literal
16+ from typing import Any , Literal , cast
1517from unittest import TestCase
1618
1719GIT_GROK_PATH = dirname (dirname (realpath (__file__ ))) + "/git-grok"
2022MAX_BRANCH_AGE = timedelta (minutes = 10 )
2123
2224
25+ @dataclass
26+ class GarbageItem :
27+ kind : Literal ["sync" , "async" ]
28+ name : str
29+ ts : str
30+
31+
32+ @dataclass
33+ class GarbageTask :
34+ kind : Literal ["sync" , "async" ]
35+ task : Any
36+
37+
2338def import_path (path : str ):
2439 module_name = basename (path ).replace ("-" , "_" )
2540 spec = spec_from_loader (module_name , SourceFileLoader (module_name , path ))
@@ -91,8 +106,13 @@ def git_init_and_cd_to_test_dir(
91106 )
92107
93108 check_output_x ("git" , "fetch" )
94- old_branches = [
95- {"name" : name .replace (f"{ TEST_REMOTE } /" , "" ), "date" : date }
109+
110+ branches = [
111+ GarbageItem (
112+ kind = kind ,
113+ name = name .replace (f"{ TEST_REMOTE } /" , "" ),
114+ ts = date ,
115+ )
96116 for b in check_output_x (
97117 "git" ,
98118 "for-each-ref" ,
@@ -101,15 +121,23 @@ def git_init_and_cd_to_test_dir(
101121 ).splitlines ()
102122 for name , date in [b .split ()]
103123 if (
104- name != f"{ TEST_REMOTE } /main"
105- and datetime .now (timezone .utc )
106- - datetime .fromisoformat (date .replace ("Z" , "+00:00" ))
107- > MAX_BRANCH_AGE
124+ kind := (
125+ ("grok/" in name and initial_branch in name and "sync" )
126+ or (
127+ name != f"{ TEST_REMOTE } /main"
128+ and from_now (date ) > MAX_BRANCH_AGE
129+ and "async"
130+ )
131+ or None
132+ )
108133 )
109- or ("grok/" in name and initial_branch in name )
110134 ]
111- old_prs = [
112- pr
135+ prs = [
136+ GarbageItem (
137+ kind = kind ,
138+ name = str (pr ["number" ]),
139+ ts = str (pr ["updatedAt" ]),
140+ )
113141 for pr in json .loads (
114142 check_output_x (
115143 "gh" ,
@@ -122,28 +150,37 @@ def git_init_and_cd_to_test_dir(
122150 )
123151 )
124152 if (
125- datetime .now (timezone .utc )
126- - datetime .fromisoformat (pr ["updatedAt" ].replace ("Z" , "+00:00" ))
127- > MAX_BRANCH_AGE
153+ kind := (
154+ (initial_branch in pr ["headRefName" ] and "sync" )
155+ or (from_now (pr ["updatedAt" ]) > MAX_BRANCH_AGE and "async" )
156+ or None
157+ )
128158 )
129- or initial_branch in pr ["headRefName" ]
130159 ]
160+
131161 tasks = [
132- git_grok .Task (check_output_x , "gh" , "pr" , "close" , str (pr ["number" ]))
133- for pr in old_prs
134- ]
135- if old_branches :
136- tasks .append (
137- git_grok .Task (
162+ GarbageTask (
163+ kind = pr .kind ,
164+ task = git_grok .Task (check_output_x , "gh" , "pr" , "close" , pr .name ),
165+ )
166+ for pr in prs
167+ ] + [
168+ GarbageTask (
169+ kind = cast (Any , kind ),
170+ task = git_grok .Task (
138171 check_output_x ,
139172 "git" ,
140173 "push" ,
141174 TEST_REMOTE ,
142175 "--delete" ,
143- * [branch [ " name" ] for branch in old_branches ],
144- )
176+ * [branch . name for branch in group ],
177+ ),
145178 )
146- [task .wait () for task in tasks if task ]
179+ for kind , group in groupby (branches , key = lambda branch : branch .kind )
180+ ]
181+ [task .task .wait () for task in tasks if task .kind == "sync" ]
182+ # Leave async tasks work in background and swallow errors (e.g. in case a
183+ # branch has already been deleted by a parallel test run or so).
147184
148185
149186def git_touch (file : str ):
@@ -234,4 +271,10 @@ def git_init_and_cd_to_test_dir(
234271 )
235272
236273
274+ def from_now (ts : str ) -> timedelta :
275+ return datetime .now (timezone .utc ) - datetime .fromisoformat (
276+ ts .replace ("Z" , "+00:00" )
277+ )
278+
279+
237280git_grok = import_path (GIT_GROK_PATH )
0 commit comments