diff --git a/gita/__main__.py b/gita/__main__.py index de818a1..ba0d270 100644 --- a/gita/__main__.py +++ b/gita/__main__.py @@ -46,7 +46,7 @@ def f_ll(args: argparse.Namespace): """ repos = utils.get_repos() if args.group: # only display repos in this group - group_repos = utils.get_groups()[args.group].split() + group_repos = utils.get_groups()[args.group] repos = {k: repos[k] for k in group_repos if k in repos} for line in utils.describe(repos): print(line) @@ -66,15 +66,15 @@ def f_group(args: argparse.Namespace): if args.to_group: gname = input('group name? ') if gname in groups: - gname_repos = set(groups[gname].split()) + gname_repos = set(groups[gname]) gname_repos.update(args.to_group) - groups[gname] = ' '.join(sorted(gname_repos)) + groups[gname] = sorted(gname_repos) utils.write_to_groups_file(groups, 'w') else: - utils.write_to_groups_file({gname: ' '.join(sorted(args.to_group))}, 'a+') + utils.write_to_groups_file({gname: sorted(args.to_group)}, 'a+') else: for group, repos in groups.items(): - print(f"{group}: {repos}") + print(f"{group}: {', '.join(repos)}") def f_ungroup(args: argparse.Namespace): @@ -82,9 +82,9 @@ def f_ungroup(args: argparse.Namespace): to_ungroup = set(args.to_ungroup) to_del = [] for name, repos in groups.items(): - remaining = set(repos.split()) - to_ungroup + remaining = set(repos) - to_ungroup if remaining: - groups[name] = ''.join(remaining) + groups[name] = list(sorted(remaining)) else: to_del.append(name) for name in to_del: @@ -117,7 +117,7 @@ def f_git_cmd(args: argparse.Namespace): if k in repos: chosen[k] = repos[k] if k in groups: - for r in groups[k].split(): + for r in groups[k]: chosen[r] = repos[r] repos = chosen cmds = ['git'] + args.cmd @@ -232,7 +232,8 @@ def main(argv=None): p_group.set_defaults(func=f_group) p_ungroup = subparsers.add_parser( - 'ungroup', help='remove group information for repos') + 'ungroup', help='remove group information for repos', + description="Remove group information on repos") p_ungroup.add_argument('to_ungroup', nargs='+', choices=utils.get_repos(), diff --git a/gita/utils.py b/gita/utils.py index 4fdc267..d14484a 100644 --- a/gita/utils.py +++ b/gita/utils.py @@ -43,7 +43,7 @@ def get_repos() -> Dict[str, str]: @lru_cache() -def get_groups() -> Dict[str, str]: +def get_groups() -> Dict[str, List[str]]: """ Return a `dict` of group name to repo names. """ @@ -98,7 +98,6 @@ def rename_repo(repos: Dict[str, str], repo: str, new_name: str): write_to_repo_file(repos, 'w') -# TODO: combine these 2 write apis def write_to_repo_file(repos: Dict[str, str], mode: str): """ """ @@ -109,15 +108,14 @@ def write_to_repo_file(repos: Dict[str, str], mode: str): f.write(data) -def write_to_groups_file(groups: Dict[str, str], mode: str): +def write_to_groups_file(groups: Dict[str, List[str]], mode: str): """ """ - data = ''.join(f'{group}: {repos}\n' for group, repos in groups.items()) fname = get_config_fname('groups.yml') os.makedirs(os.path.dirname(fname), exist_ok=True) with open(fname, mode) as f: - f.write(data) + yaml.dump(groups, f, default_flow_style=None) def add_repos(repos: Dict[str, str], new_paths: List[str]): diff --git a/setup.py b/setup.py index 257b038..e6e3984 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='gita', packages=['gita'], - version='0.10.8', + version='0.10.9', license='MIT', description='Manage multiple git repos', long_description=long_description, @@ -32,6 +32,7 @@ "Topic :: Utilities", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], include_package_data=True, ) diff --git a/tests/conftest.py b/tests/conftest.py index 90d0bab..b3e59ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,18 @@ -import os +from pathlib import Path from unittest.mock import MagicMock -TEST_DIR = os.path.abspath(os.path.dirname(__file__)) -PATH_FNAME = os.path.join(TEST_DIR, 'mock_path_file') -PATH_FNAME_EMPTY = os.path.join(TEST_DIR, 'empty_path_file') -PATH_FNAME_CLASH = os.path.join(TEST_DIR, 'clash_path_file') +TEST_DIR = Path(__file__).parents[0] +def fullpath(fname: str): + return str(TEST_DIR / fname) + + +PATH_FNAME = fullpath('mock_path_file') +PATH_FNAME_EMPTY = fullpath('empty_path_file') +PATH_FNAME_CLASH = fullpath('clash_path_file') +GROUP_FNAME = fullpath('mock_group_file') + def async_mock(): """ Mock an async function. The calling arguments are saved in a MagicMock. diff --git a/tests/mock_group_file b/tests/mock_group_file new file mode 100644 index 0000000..32f0a64 --- /dev/null +++ b/tests/mock_group_file @@ -0,0 +1,2 @@ +xx: [a, b] +yy: [a, c, d] diff --git a/tests/test_main.py b/tests/test_main.py index e66ee89..1946352 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -5,7 +5,10 @@ from gita import __main__ from gita import utils -from conftest import PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH, async_mock +from conftest import ( + PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH, GROUP_FNAME, + async_mock +) class TestLsLl: @@ -128,6 +131,34 @@ def test_superman(mock_run, _, input): mock_run.assert_called_once_with(expected_cmds, cwd='path7') +@pytest.mark.parametrize('input, expected', [ + ('a', {'xx': ['b'], 'yy': ['c', 'd']}), + ("c", {'xx': ['a', 'b'], 'yy': ['a', 'd']}), + ("a b", {'yy': ['c', 'd']}), +]) +@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''}) +@patch('gita.utils.get_config_fname', return_value=GROUP_FNAME) +@patch('gita.utils.write_to_groups_file') +def test_ungroup(mock_write, _, __, input, expected): + utils.get_groups.cache_clear() + args = ['ungroup'] + shlex.split(input) + __main__.main(args) + mock_write.assert_called_once_with(expected, 'w') + + +@patch('gita.utils.is_git', return_value=True) +@patch('gita.utils.get_config_fname', return_value=PATH_FNAME) +@patch('gita.utils.rename_repo') +def test_rename(mock_rename, _, __): + utils.get_repos.cache_clear() + args = ['rename', 'repo1', 'abc'] + __main__.main(args) + mock_rename.assert_called_once_with( + {'repo1': '/a/bcd/repo1', 'repo2': '/e/fgh/repo2', + 'xxx': '/a/b/c/repo3'}, + 'repo1', 'abc') + + @patch('os.path.isfile', return_value=False) def test_info(mock_isfile, capfd): __main__.f_info(None) diff --git a/tests/test_utils.py b/tests/test_utils.py index 2cd2518..3128041 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,7 +3,9 @@ from unittest.mock import patch, mock_open from gita import utils, info -from conftest import PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH +from conftest import ( + PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH, GROUP_FNAME, +) @pytest.mark.parametrize('test_input, diff_return, expected', [ @@ -43,8 +45,17 @@ def test_describe(test_input, diff_return, expected, monkeypatch): def test_get_repos(mock_path_fname, _, path_fname, expected): mock_path_fname.return_value = path_fname utils.get_repos.cache_clear() - repos = utils.get_repos() - assert repos == expected + assert utils.get_repos() == expected + + +@pytest.mark.parametrize('group_fname, expected', [ + (GROUP_FNAME, {'xx': ['a', 'b'], 'yy': ['a', 'c', 'd']}), +]) +@patch('gita.utils.get_config_fname') +def test_get_groups(mock_group_fname, group_fname, expected): + mock_group_fname.return_value = group_fname + utils.get_groups.cache_clear() + assert utils.get_groups() == expected @patch('os.path.isfile', return_value=True)