Skip to content

Commit 996e843

Browse files
committed
asyncio.gather
coverage coverage pep8
1 parent 2a6c644 commit 996e843

21 files changed

+217
-251
lines changed

.travis.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ git:
66
depth: 25
77
quiet: true
88

9+
python: 3.7
10+
911
matrix:
1012
include:
1113
- os: linux
1214
python: 3.6
1315
- os: linux
1416
name: Integration install
15-
python: 3.7
1617
install:
1718
- python setup.py install
1819
- pip install gitutils[tests]
@@ -21,8 +22,7 @@ matrix:
2122
- python -m pytest $TRAVIS_BUILD_DIR/tests -r a -v
2223
- os: linux
2324
name: PEP8 MyPy Coverage
24-
python: 3.7
25-
install: pip install -e .[tests,cov]
25+
install: pip install -e .[tests,cov,github]
2626
script:
2727
- flake8
2828
- mypy .

RemoveCollaborators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def main():
5353
if modify:
5454
if repo.archived:
5555
logging.error(f'could not remove collabs from archived {repo.full_name}')
56-
webbrowser.open_new_tab('https://github.com/'+repo.full_name+'/settings')
56+
webbrowser.open_new_tab('https://github.com/' + repo.full_name + '/settings')
5757
continue
5858

5959
for admin in admins:

branch.py

+9-24
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,25 @@
33
report on git repos not on the expected branch e.g. 'master'
44
"""
55
from argparse import ArgumentParser
6-
from gitutils import findbranch
7-
from pathlib import Path
8-
import asyncio
9-
import os
10-
import sys
11-
12-
13-
async def find_branch(branch: str, path: Path):
14-
path = Path(path).expanduser()
15-
16-
async for b in findbranch(branch, path):
17-
print(b[0], ' => ', b[1])
6+
import logging
7+
from gitutils.branch import coro_local
8+
from gitutils.runner import runner
189

1910

2011
def main():
2112
p = ArgumentParser()
2213
p.add_argument('codepath', help='path to code root', nargs='?', default='~/code')
2314
p.add_argument('mainbranch', nargs='?',
2415
default='master', help='name of your main branch')
16+
p.add_argument('-v', '--verbose', action='store_true')
2517
P = p.parse_args()
2618

27-
if os.name == 'nt' and (3, 7) <= sys.version_info < (3, 8):
28-
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
19+
if P.verbose:
20+
logging.basicConfig(level=logging.DEBUG)
2921

30-
if sys.version_info >= (3, 7):
31-
asyncio.run(find_branch(P.mainbranch, P.codepath))
32-
else:
33-
if os.name == 'nt':
34-
loop = asyncio.ProactorEventLoop()
35-
else:
36-
loop = asyncio.new_event_loop()
37-
asyncio.get_child_watcher().attach_loop(loop)
38-
loop.run_until_complete(find_branch(P.mainbranch, P.codepath))
39-
loop.close()
22+
branches = runner(coro_local, P.mainbranch, P.codepath)
23+
for b in branches:
24+
print(b[0], ' => ', b[1])
4025

4126

4227
if __name__ == '__main__':

check.py

+4-17
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
for a root directory, assumes all subdirectories are Git repos
44
and "git fetch --dry-run" each
55
"""
6-
import sys
7-
import os
86
import logging
9-
import asyncio
107
from argparse import ArgumentParser
118

12-
from gitutils.pull import find_remote
9+
from gitutils.pull import coro_remote
10+
from gitutils.runner import runner
1311

1412
MODE = ['fetch', '--dry-run']
1513

@@ -23,19 +21,8 @@ def main():
2321
if P.verbose:
2422
logging.basicConfig(level=logging.DEBUG)
2523

26-
if os.name == 'nt' and (3, 7) <= sys.version_info < (3, 8):
27-
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
28-
29-
if sys.version_info >= (3, 7):
30-
asyncio.run(find_remote(MODE, P.codepath))
31-
else:
32-
if os.name == 'nt':
33-
loop = asyncio.ProactorEventLoop()
34-
else:
35-
loop = asyncio.new_event_loop()
36-
asyncio.get_child_watcher().attach_loop(loop)
37-
loop.run_until_complete(find_remote(MODE, P.codepath))
38-
loop.close()
24+
remotes = runner(coro_remote, MODE, P.codepath)
25+
print('\n'.join(map(str, remotes)))
3926

4027

4128
if __name__ == '__main__':

fetch.py

+4-17
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
for a root directory, assumes all subdirectories are Git repos
44
and "git fetch" each of them.
55
"""
6-
import sys
7-
import os
86
import logging
9-
import asyncio
107
from argparse import ArgumentParser
118

12-
from gitutils.pull import find_remote
9+
from gitutils.pull import coro_remote
10+
from gitutils.runner import runner
1311

1412
MODE = 'fetch'
1513

@@ -23,19 +21,8 @@ def main():
2321
if P.verbose:
2422
logging.basicConfig(level=logging.DEBUG)
2523

26-
if os.name == 'nt' and (3, 7) <= sys.version_info < (3, 8):
27-
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
28-
29-
if sys.version_info >= (3, 7):
30-
asyncio.run(find_remote(MODE, P.codepath))
31-
else:
32-
if os.name == 'nt':
33-
loop = asyncio.ProactorEventLoop()
34-
else:
35-
loop = asyncio.new_event_loop()
36-
asyncio.get_child_watcher().attach_loop(loop)
37-
loop.run_until_complete(find_remote(MODE, P.codepath))
38-
loop.close()
24+
remotes = runner(coro_remote, MODE, P.codepath)
25+
print('\n'.join(map(str, remotes)))
3926

4027

4128
if __name__ == '__main__':

find_missing_file.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66
from pathlib import Path
77
from sys import stderr
8-
from gitutils import find_dir_missing_file
8+
from gitutils.find import find_dir_missing_file
99
from argparse import ArgumentParser
1010

1111

gitemail.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from typing import Sequence
2020
from argparse import ArgumentParser
2121

22-
from gitutils import gitemail
22+
from gitutils.email import gitemail
2323
from gitutils.git import MAGENTA, BLACK
2424

2525
cwd = Path(__file__).parent

gitutils/__init__.py

+1-41
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,5 @@
33
Speed is emphasized throughout, with pipelining and concurrent `asyncio` routines throughout
44
for fastest operation on large numbers of repos.
55
"""
6-
from pathlib import Path
7-
from typing import List
8-
import shutil
96

10-
from .git import listchanged # noqa: F401
11-
from .branch import findbranch # noqa: F401
12-
from .email import gitemail # noqa: F401
13-
from .pull import fetchpull # noqa: F401
14-
15-
16-
def find_dir_missing_file(fn: str, path: Path, copyfile: Path = None) -> List[Path]:
17-
"""
18-
if directory is missing a file, copy the file to that directory
19-
20-
Parameters
21-
----------
22-
fn : pathlib.Path
23-
filename to look for
24-
path : pathlib.Path
25-
top-level directory to check directories under
26-
copyfile : pathlib.Path, optional
27-
if present, copy this file into the directory that doesn't have it
28-
29-
Results
30-
-------
31-
missing : list of pathlib.Path
32-
directories that were missing the file and it wasn't copied there.
33-
"""
34-
path = Path(path).expanduser()
35-
36-
dlist = (x for x in path.iterdir() if x.is_dir())
37-
38-
missing: List[Path] = []
39-
for d in dlist:
40-
if not (d / fn).is_file():
41-
if isinstance(copyfile, Path):
42-
shutil.copy(copyfile, d)
43-
print('copied', copyfile, 'to', d)
44-
else:
45-
missing.append(d)
46-
47-
return missing
7+
from .runner import runner # noqa: F401

gitutils/branch.py

+19-16
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
https://stackoverflow.com/a/45028375
66
"""
77

8-
from typing import AsyncGenerator, Tuple
8+
import typing
99
from pathlib import Path
1010
import asyncio
11+
import logging
1112

1213
from .git import GITEXE, gitdirs
1314

@@ -17,20 +18,20 @@
1718
BRANCH_SIMPLE = ['branch', '--show-current'] # Git >= 2.22
1819

1920

20-
async def findbranch(mainbranch: str, rdir: Path) -> AsyncGenerator[Tuple[str, str], None]:
21+
async def different_branch(mainbranch: str, path: Path) -> typing.Tuple[str, str]:
2122
"""
22-
find all branches in tree not matching "mainbranch"
23+
does branch not match "mainbranch"
2324
2425
Parameters
2526
----------
2627
2728
mainbranch : str
2829
branch name that's "normal" e.g. master
29-
rdir : pathlib.Path
30-
top-level directory to work under e.g. ~/code/
30+
path : pathlib.Path
31+
Git repo to check
3132
32-
Yields
33-
------
33+
Returns
34+
-------
3435
3536
branch : tuple of pathlib.Path, str
3637
repo path and branch name
@@ -39,16 +40,18 @@ async def findbranch(mainbranch: str, rdir: Path) -> AsyncGenerator[Tuple[str, s
3940
https://docs.python.org/3/library/asyncio-subprocess.html#subprocess-and-threads
4041
"""
4142

42-
rdir = Path(rdir).expanduser()
43+
proc = await asyncio.create_subprocess_exec(*[GITEXE, '-C', str(path)] + BRANCH_REV,
44+
stdout=asyncio.subprocess.PIPE)
45+
stdout, _ = await proc.communicate()
46+
logging.info(str(path))
4347

44-
for d in gitdirs(rdir):
45-
proc = await asyncio.create_subprocess_exec(*[GITEXE, '-C', str(d)] + BRANCH_REV,
46-
stdout=asyncio.subprocess.PIPE)
47-
stdout, _ = await proc.communicate()
48+
branchname = stdout.decode('utf8').rstrip()
4849

49-
branchname = stdout.decode('utf8').rstrip()
50+
if mainbranch != branchname:
51+
return path.name, branchname
52+
return None
5053

51-
if mainbranch in branchname:
52-
continue
5354

54-
yield d.name, branchname
55+
async def coro_local(branch: str, path: Path) -> typing.List[Path]:
56+
futures = [different_branch(branch, d) for d in gitdirs(path)]
57+
return list(filter(None, await asyncio.gather(*futures)))

gitutils/find.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from pathlib import Path
2+
import typing
3+
import shutil
4+
5+
6+
def find_dir_missing_file(fn: str, path: Path, copyfile: Path = None) -> typing.List[Path]:
7+
"""
8+
if directory is missing a file, copy the file to that directory
9+
10+
Parameters
11+
----------
12+
fn : str
13+
filename to look for
14+
path : pathlib.Path
15+
top-level directory to check directories under
16+
copyfile : pathlib.Path, optional
17+
if present, copy this file into the directory that doesn't have it
18+
19+
Results
20+
-------
21+
missing : list of pathlib.Path
22+
directories that were missing the file and it wasn't copied there.
23+
"""
24+
path = Path(path).expanduser()
25+
if not path.is_dir():
26+
raise NotADirectoryError(path)
27+
28+
dlist = (x for x in path.iterdir() if x.is_dir())
29+
30+
missing = [] # type: typing.List[Path]
31+
for d in dlist:
32+
if not (d / fn).is_file():
33+
if isinstance(copyfile, Path):
34+
shutil.copy(copyfile, d)
35+
print('copied', copyfile, 'to', d)
36+
else:
37+
missing.append(d)
38+
39+
return missing

gitutils/git.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
from pathlib import Path
55
import subprocess
6-
from typing import List, Iterator, Any
6+
import typing
77
import shutil
88

99
try:
@@ -15,9 +15,9 @@
1515
MAGENTA = BLACK = ''
1616

1717
TIMEOUT = 30. # arbitrary, seconds
18-
GITEXE: Any = shutil.which('git') # type: ignore
18+
GITEXE = shutil.which('git') # type: str
1919
if not GITEXE:
20-
raise FileNotFoundError('Could not find executable for Git')
20+
raise ImportError('Could not find executable for Git')
2121

2222
"""
2323
replaced by git status --porcelain:
@@ -28,7 +28,7 @@
2828
"""
2929

3030

31-
def gitdirs(path: Path) -> Iterator[Path]:
31+
def gitdirs(path: Path) -> typing.Iterator[Path]:
3232
"""
3333
Generator for Git directories
3434
@@ -77,13 +77,13 @@ def baddir(path: Path) -> bool:
7777

7878
try:
7979
bad = (path / '.nogit').is_file() or not (path / '.git' / 'HEAD').is_file()
80-
except PermissionError:
80+
except PermissionError: # Windows
8181
bad = True
8282

8383
return bad
8484

8585

86-
def listchanged(path: Path) -> List[str]:
86+
def listchanged(path: Path) -> typing.List[str]:
8787
"""very quick check if any files were modified in this Git repo
8888
8989
Parameters

0 commit comments

Comments
 (0)