1
0
Fork 0

Adding upstream version 0.16.2.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-11 18:46:56 +01:00
parent 54898c7244
commit 149ef1ff29
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
8 changed files with 832 additions and 588 deletions

View file

@ -70,8 +70,11 @@ The bookkeeping sub-commands are
and automatically generate hierarchical groups. See the [customization section](#custom) for more details. and automatically generate hierarchical groups. See the [customization section](#custom) for more details.
- `gita add -b <bare-repo-path(s)>`: add bare repo(s) to `gita`. See the [customization section](#custom) for more details on setting custom worktree. - `gita add -b <bare-repo-path(s)>`: add bare repo(s) to `gita`. See the [customization section](#custom) for more details on setting custom worktree.
- `gita add -r <repo-parent-path(s)>`: add repo(s) in <repo-parent-path(s)> recursively - `gita add -r <repo-parent-path(s)>`: add repo(s) in <repo-parent-path(s)> recursively
- `gita clone <config-file>`: clone repos in `config-file` (generated by `gita freeze`) to current directory. - `gita clear`: remove all groups and repos
- `gita clone -p <config-file>`: clone repos in `config-file` to prescribed paths. - `gita clone <URL>`: clone repo from `URL` at current working directory
- `gita clone <URL> -C <directory>`: change to `directory` and then clone repo
- `gita clone -f <config-file>`: clone repos in `config-file` (generated by `gita freeze`) to current directory.
- `gita clone -p -f <config-file>`: clone repos in `config-file` to prescribed paths.
- `gita context`: context sub-command - `gita context`: context sub-command
- `gita context`: show current context - `gita context`: show current context
- `gita context <group-name>`: set context to `group-name`, all operations then only apply to repos in this group - `gita context <group-name>`: set context to `group-name`, all operations then only apply to repos in this group

View file

@ -57,8 +57,11 @@
- `gita add -b <bare-repo-path(s)>`: 添加bare库 - `gita add -b <bare-repo-path(s)>`: 添加bare库
[customization section](#custom) [customization section](#custom)
- `gita add -r <repo-parent-path(s)>`: 递归添加路径下的所有库 - `gita add -r <repo-parent-path(s)>`: 递归添加路径下的所有库
- `gita clone <config-file>`: 克隆`<config-file>` (由`gita freeze`生成)里的库 - `gita clear`:
- `gita clone -p <config-file>`: 克隆`<config-file>`里的库并放到指定路径 - `gita clone <URL>`:
- `gita clone <URL> -C <directory>`:
- `gita clone -f <config-file>`: 克隆`<config-file>` (由`gita freeze`生成)里的库
- `gita clone -p -f <config-file>`: 克隆`<config-file>`里的库并放到指定路径
- `gita context`: 情境命令 - `gita context`: 情境命令
- `gita context`: 显示当前的情境 - `gita context`: 显示当前的情境
- `gita context none`: 去除情境 - `gita context none`: 去除情境

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Before After
Before After

File diff suppressed because it is too large Load diff

View file

@ -47,6 +47,7 @@
}, },
"push":{ "push":{
"cmd": "git push", "cmd": "git push",
"allow_all": true,
"help": "push the local updates" "help": "push the local updates"
}, },
"rebase":{ "rebase":{

View file

@ -279,6 +279,7 @@ def _make_name(path: str, repos: Dict[str, Dict[str, str]],
def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str], def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str],
include_bare=False, include_bare=False,
exclude_submodule=False, exclude_submodule=False,
dry_run=False,
) -> Dict[str, Dict[str, str]]: ) -> Dict[str, Dict[str, str]]:
""" """
Write new repo paths to file; return the added repos. Write new repo paths to file; return the added repos.
@ -291,6 +292,10 @@ def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str],
new_repos = {} new_repos = {}
if new_paths: if new_paths:
print(f"Found {len(new_paths)} new repo(s).") print(f"Found {len(new_paths)} new repo(s).")
if dry_run:
for p in new_paths:
print(p)
return {}
name_counts = Counter( name_counts = Counter(
os.path.basename(os.path.normpath(p)) for p in new_paths os.path.basename(os.path.normpath(p)) for p in new_paths
) )
@ -453,14 +458,14 @@ def get_cmds_from_files() -> Dict[str, Dict[str, str]]:
return cmds return cmds
def parse_repos_and_rest(input: List[str] def parse_repos_and_rest(input: List[str], quote_mode=False,
) -> Tuple[Dict[str, Dict[str, str]], List[str]]: ) -> Tuple[Dict[str, Dict[str, str]], List[str]]:
""" """
Parse gita input arguments Parse gita input arguments
@return: repos and the rest (e.g., gita shell and super commands) @return: repos and the rest (e.g., gita shell and super commands)
""" """
i = None i = 0
names = [] names = []
repos = get_repos() repos = get_repos()
groups = get_groups() groups = get_groups()
@ -475,6 +480,10 @@ def parse_repos_and_rest(input: List[str]
i += 1 i += 1
if not names and ctx: if not names and ctx:
names = [ctx.stem] names = [ctx.stem]
if quote_mode and i + 1 != len(input):
print(input[i], 'is not a repo or group' )
sys.exit(2)
if names: if names:
chosen = {} chosen = {}
for k in names: for k in names:

View file

@ -1,24 +1,24 @@
from setuptools import setup from setuptools import setup
long_description = None long_description = None
with open('README.md', encoding='utf-8') as f: with open("README.md", encoding="utf-8") as f:
long_description = f.read() long_description = f.read()
setup( setup(
name='gita', name="gita",
packages=['gita'], packages=["gita"],
version='0.16.1', version="0.16.2",
license='MIT', license="MIT",
description='Manage multiple git repos with sanity', description="Manage multiple git repos with sanity",
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type="text/markdown",
url='https://github.com/nosarthur/gita', url="https://github.com/nosarthur/gita",
platforms=['linux', 'osx', 'win32'], platforms=["linux", "osx", "win32"],
keywords=['git', 'manage multiple repositories', 'cui', 'command-line'], keywords=["git", "manage multiple repositories", "cui", "command-line"],
author='Dong Zhou', author="Dong Zhou",
author_email='zhou.dong@gmail.com', author_email="zhou.dong@gmail.com",
entry_points={'console_scripts': ['gita = gita.__main__:main']}, entry_points={"console_scripts": ["gita = gita.__main__:main"]},
python_requires='~=3.6', python_requires="~=3.6",
classifiers=[ classifiers=[
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Intended Audience :: Developers", "Intended Audience :: Developers",

View file

@ -1,4 +1,3 @@
import os
import pytest import pytest
from unittest.mock import patch from unittest.mock import patch
from pathlib import Path from pathlib import Path
@ -7,312 +6,368 @@ import asyncio
import shlex import shlex
from gita import __main__ from gita import __main__
from gita import utils, info, common from gita import utils, info
from conftest import ( from conftest import (
PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH, GROUP_FNAME, PATH_FNAME,
async_mock, TEST_DIR, PATH_FNAME_EMPTY,
PATH_FNAME_CLASH,
GROUP_FNAME,
async_mock,
TEST_DIR,
) )
@patch('gita.utils.get_repos', return_value={'aa'}) @patch("gita.utils.get_repos", return_value={"aa"})
def test_group_name(_): def test_group_name(_):
got = __main__._group_name('xx') got = __main__._group_name("xx")
assert got == 'xx' assert got == "xx"
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
__main__._group_name('aa') __main__._group_name("aa")
class TestAdd: class TestAdd:
@pytest.mark.parametrize(
@pytest.mark.parametrize('input, expected', [ "input, expected",
(['add', '.'], ''), [
]) (["add", "."], ""),
@patch('gita.common.get_config_fname') ],
)
@patch("gita.common.get_config_fname")
def test_add(self, mock_path_fname, tmp_path, input, expected): def test_add(self, mock_path_fname, tmp_path, input, expected):
def side_effect(input, _=None): def side_effect(input, _=None):
return tmp_path / f'{input}.txt' return tmp_path / f"{input}.txt"
mock_path_fname.side_effect = side_effect mock_path_fname.side_effect = side_effect
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
__main__.main(input) __main__.main(input)
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
got = utils.get_repos() got = utils.get_repos()
assert len(got) == 1 assert len(got) == 1
assert got['gita']['type'] == expected assert got["gita"]["type"] == expected
@pytest.mark.parametrize('path_fname, expected', [ @pytest.mark.parametrize(
(PATH_FNAME, ''), "path_fname, expected",
[
(PATH_FNAME, ""),
(PATH_FNAME_CLASH, "repo2: ['--haha', '--pp']\n"), (PATH_FNAME_CLASH, "repo2: ['--haha', '--pp']\n"),
]) ],
@patch('gita.utils.is_git', return_value=True) )
@patch('gita.utils.get_groups', return_value={}) @patch("gita.utils.is_git", return_value=True)
@patch('gita.common.get_config_fname') @patch("gita.utils.get_groups", return_value={})
@patch("gita.common.get_config_fname")
def test_flags(mock_path_fname, _, __, path_fname, expected, capfd): def test_flags(mock_path_fname, _, __, path_fname, expected, capfd):
mock_path_fname.return_value = path_fname mock_path_fname.return_value = path_fname
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
__main__.main(['flags']) __main__.main(["flags"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert out == expected assert out == expected
class TestLsLl: class TestLsLl:
@patch('gita.common.get_config_fname') @patch("gita.common.get_config_fname")
def test_ll(self, mock_path_fname, capfd, tmp_path): def test_ll(self, mock_path_fname, capfd, tmp_path):
""" """
functional test functional test
""" """
# avoid modifying the local configuration # avoid modifying the local configuration
def side_effect(input, _=None): def side_effect(input, _=None):
return tmp_path / f'{input}.txt' return tmp_path / f"{input}.txt"
mock_path_fname.side_effect = side_effect mock_path_fname.side_effect = side_effect
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
__main__.main(['add', '.']) __main__.main(["add", "."])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'Found 1 new repo(s).\n' == out assert "Found 1 new repo(s).\n" == out
# in production this is not needed # in production this is not needed
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
__main__.main(['ls']) __main__.main(["ls"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'gita\n' == out assert "gita\n" == out
__main__.main(['ll']) __main__.main(["ll"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'gita' in out assert "gita" in out
assert info.Color.end in out assert info.Color.end in out
# no color on branch name # no color on branch name
__main__.main(['ll', '-C']) __main__.main(["ll", "-C"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'gita' in out assert "gita" in out
assert info.Color.end not in out assert info.Color.end not in out
__main__.main(['ls', 'gita']) __main__.main(["ls", "gita"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert out.strip() == utils.get_repos()['gita']['path'] assert out.strip() == utils.get_repos()["gita"]["path"]
def test_ls(self, monkeypatch, capfd): def test_ls(self, monkeypatch, capfd):
monkeypatch.setattr(utils, 'get_repos', monkeypatch.setattr(
lambda: {'repo1': {'path': '/a/'}, 'repo2': {'path': '/b/'}}) utils,
monkeypatch.setattr(utils, 'describe', lambda x: x) "get_repos",
__main__.main(['ls']) lambda: {"repo1": {"path": "/a/"}, "repo2": {"path": "/b/"}},
)
monkeypatch.setattr(utils, "describe", lambda x: x)
__main__.main(["ls"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert out == "repo1 repo2\n" assert out == "repo1 repo2\n"
__main__.main(['ls', 'repo1']) __main__.main(["ls", "repo1"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert out == '/a/\n' assert out == "/a/\n"
@pytest.mark.parametrize('path_fname, expected', [ @pytest.mark.parametrize(
(PATH_FNAME, "path_fname, expected",
"repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \nxxx cmaster dsu\x1b[0m msg \n"), [
(PATH_FNAME_EMPTY, ""), (
(PATH_FNAME_CLASH, PATH_FNAME,
"repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \n" "repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \nxxx cmaster dsu\x1b[0m msg \n",
), ),
]) (PATH_FNAME_EMPTY, ""),
@patch('gita.utils.is_git', return_value=True) (
@patch('gita.info.get_head', return_value="master") PATH_FNAME_CLASH,
@patch('gita.info._get_repo_status', return_value=("d", "s", "u", "c")) "repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \n",
@patch('gita.info.get_commit_msg', return_value="msg") ),
@patch('gita.info.get_commit_time', return_value="") ],
@patch('gita.common.get_config_fname') )
def test_with_path_files(self, mock_path_fname, _0, _1, _2, _3, _4, path_fname, @patch("gita.utils.is_git", return_value=True)
expected, capfd): @patch("gita.info.get_head", return_value="master")
@patch("gita.info._get_repo_status", return_value=("d", "s", "u", "c"))
@patch("gita.info.get_commit_msg", return_value="msg")
@patch("gita.info.get_commit_time", return_value="")
@patch("gita.common.get_config_fname")
def test_with_path_files(
self, mock_path_fname, _0, _1, _2, _3, _4, path_fname, expected, capfd
):
def side_effect(input, _=None): def side_effect(input, _=None):
if input == 'repos.csv': if input == "repos.csv":
return path_fname return path_fname
return f'/{input}' return f"/{input}"
mock_path_fname.side_effect = side_effect mock_path_fname.side_effect = side_effect
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
__main__.main(['ll']) __main__.main(["ll"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
print(out) print(out)
assert err == '' assert err == ""
assert out == expected assert out == expected
@pytest.mark.parametrize('input, expected', [ @pytest.mark.parametrize(
({'repo1': {'path': '/a/'}, 'repo2': {'path': '/b/'}}, ''), "input, expected",
]) [
@patch('subprocess.run') ({"repo1": {"path": "/a/"}, "repo2": {"path": "/b/"}}, ""),
@patch('gita.utils.get_repos') ],
)
@patch("subprocess.run")
@patch("gita.utils.get_repos")
def test_freeze(mock_repos, mock_run, input, expected, capfd): def test_freeze(mock_repos, mock_run, input, expected, capfd):
mock_repos.return_value = input mock_repos.return_value = input
__main__.main(['freeze']) __main__.main(["freeze"])
assert mock_run.call_count == 2 assert mock_run.call_count == 2
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert out == expected assert out == expected
@patch('gita.utils.parse_clone_config', return_value=[ @patch("subprocess.run")
['git@github.com:user/repo.git', 'repo', '/a/repo']]) def test_clone_with_url(mock_run):
@patch('gita.utils.run_async', new=async_mock()) args = argparse.Namespace()
@patch('subprocess.run') args.clonee = "http://abc.com/repo1"
def test_clone(*_): args.preserve_path = None
args.directory = "/home/xxx"
args.from_file = False
__main__.f_clone(args)
cmds = ["git", "clone", args.clonee]
mock_run.assert_called_once_with(cmds, cwd=args.directory)
@patch(
"gita.utils.parse_clone_config",
return_value=[["git@github.com:user/repo.git", "repo", "/a/repo"]],
)
@patch("gita.utils.run_async", new=async_mock())
@patch("subprocess.run")
def test_clone_with_config_file(*_):
asyncio.set_event_loop(asyncio.new_event_loop()) asyncio.set_event_loop(asyncio.new_event_loop())
args = argparse.Namespace() args = argparse.Namespace()
args.fname = ['freeze_filename'] args.clonee = "freeze_filename"
args.preserve_path = None args.preserve_path = False
args.directory = None
args.from_file = True
__main__.f_clone(args) __main__.f_clone(args)
mock_run = utils.run_async.mock mock_run = utils.run_async.mock
assert mock_run.call_count == 1 assert mock_run.call_count == 1
cmds = ['git', 'clone', 'git@github.com:user/repo.git'] cmds = ["git", "clone", "git@github.com:user/repo.git"]
mock_run.assert_called_once_with('repo', Path.cwd(), cmds) mock_run.assert_called_once_with("repo", Path.cwd(), cmds)
@patch('gita.utils.parse_clone_config', return_value=[ @patch(
['git@github.com:user/repo.git', 'repo', '/a/repo']]) "gita.utils.parse_clone_config",
@patch('gita.utils.run_async', new=async_mock()) return_value=[["git@github.com:user/repo.git", "repo", "/a/repo"]],
@patch('subprocess.run') )
@patch("gita.utils.run_async", new=async_mock())
@patch("subprocess.run")
def test_clone_with_preserve_path(*_): def test_clone_with_preserve_path(*_):
asyncio.set_event_loop(asyncio.new_event_loop()) asyncio.set_event_loop(asyncio.new_event_loop())
args = argparse.Namespace() args = argparse.Namespace()
args.fname = ['freeze_filename'] args.clonee = "freeze_filename"
args.directory = None
args.from_file = True
args.preserve_path = True args.preserve_path = True
__main__.f_clone(args) __main__.f_clone(args)
mock_run = utils.run_async.mock mock_run = utils.run_async.mock
assert mock_run.call_count == 1 assert mock_run.call_count == 1
cmds = ['git', 'clone', 'git@github.com:user/repo.git', '/a/repo'] cmds = ["git", "clone", "git@github.com:user/repo.git", "/a/repo"]
mock_run.assert_called_once_with('repo', Path.cwd(), cmds) mock_run.assert_called_once_with("repo", Path.cwd(), cmds)
@patch('os.makedirs') @patch("os.makedirs")
@patch('os.path.isfile', return_value=True) @patch("os.path.isfile", return_value=True)
@patch('gita.common.get_config_fname', return_value='some path') @patch("gita.common.get_config_fname", return_value="some path")
@patch('gita.utils.get_repos', return_value={'repo1': {'path': '/a/', 'type': ''}, @patch(
'repo2': {'path': '/b/', 'type': ''}}) "gita.utils.get_repos",
@patch('gita.utils.write_to_repo_file') return_value={
"repo1": {"path": "/a/", "type": ""},
"repo2": {"path": "/b/", "type": ""},
},
)
@patch("gita.utils.write_to_repo_file")
def test_rm(mock_write, *_): def test_rm(mock_write, *_):
args = argparse.Namespace() args = argparse.Namespace()
args.repo = ['repo1'] args.repo = ["repo1"]
__main__.f_rm(args) __main__.f_rm(args)
mock_write.assert_called_once_with( mock_write.assert_called_once_with({"repo2": {"path": "/b/", "type": ""}}, "w")
{'repo2': {'path': '/b/', 'type': ''}}, 'w')
def test_not_add(): def test_not_add():
# this won't write to disk because the repo is not valid # this won't write to disk because the repo is not valid
__main__.main(['add', '/home/some/repo/']) __main__.main(["add", "/home/some/repo/"])
@patch('gita.utils.get_repos', return_value={'repo2': {'path': '/d/efg', @patch("gita.utils.get_repos", return_value={"repo2": {"path": "/d/efg", "flags": []}})
'flags': []}}) @patch("subprocess.run")
@patch('subprocess.run')
def test_fetch(mock_run, *_): def test_fetch(mock_run, *_):
asyncio.set_event_loop(asyncio.new_event_loop()) asyncio.set_event_loop(asyncio.new_event_loop())
__main__.main(['fetch']) __main__.main(["fetch"])
mock_run.assert_called_once_with(['git', 'fetch'], cwd='/d/efg', shell=False) mock_run.assert_called_once_with(["git", "fetch"], cwd="/d/efg", shell=False)
@patch( @patch(
'gita.utils.get_repos', return_value={ "gita.utils.get_repos",
'repo1': {'path': '/a/bc', 'flags': []}, return_value={
'repo2': {'path': '/d/efg', 'flags': []} "repo1": {"path": "/a/bc", "flags": []},
}) "repo2": {"path": "/d/efg", "flags": []},
@patch('gita.utils.run_async', new=async_mock()) },
@patch('subprocess.run') )
@patch("gita.utils.run_async", new=async_mock())
@patch("subprocess.run")
def test_async_fetch(*_): def test_async_fetch(*_):
__main__.main(['fetch']) __main__.main(["fetch"])
mock_run = utils.run_async.mock mock_run = utils.run_async.mock
assert mock_run.call_count == 2 assert mock_run.call_count == 2
cmds = ['git', 'fetch'] cmds = ["git", "fetch"]
# print(mock_run.call_args_list) # print(mock_run.call_args_list)
mock_run.assert_any_call('repo1', '/a/bc', cmds) mock_run.assert_any_call("repo1", "/a/bc", cmds)
mock_run.assert_any_call('repo2', '/d/efg', cmds) mock_run.assert_any_call("repo2", "/d/efg", cmds)
@pytest.mark.parametrize('input', [ @pytest.mark.parametrize(
'diff --name-only --staged', "input",
[
"diff --name-only --staged",
"commit -am 'lala kaka'", "commit -am 'lala kaka'",
]) ],
@patch('gita.utils.get_repos', return_value={'repo7': {'path': 'path7', 'flags': []}}) )
@patch('subprocess.run') @patch("gita.utils.get_repos", return_value={"repo7": {"path": "path7", "flags": []}})
@patch("subprocess.run")
def test_superman(mock_run, _, input): def test_superman(mock_run, _, input):
mock_run.reset_mock() mock_run.reset_mock()
args = ['super', 'repo7'] + shlex.split(input) args = ["super", "repo7"] + shlex.split(input)
__main__.main(args) __main__.main(args)
expected_cmds = ['git'] + shlex.split(input) expected_cmds = ["git"] + shlex.split(input)
mock_run.assert_called_once_with(expected_cmds, cwd='path7', shell=False) mock_run.assert_called_once_with(expected_cmds, cwd="path7", shell=False)
@pytest.mark.parametrize('input', [ @pytest.mark.parametrize(
'diff --name-only --staged', "input",
[
"diff --name-only --staged",
"commit -am 'lala kaka'", "commit -am 'lala kaka'",
]) ],
@patch('gita.utils.get_repos', return_value={'repo7': {'path': 'path7', 'flags': []}}) )
@patch('subprocess.run') @patch("gita.utils.get_repos", return_value={"repo7": {"path": "path7", "flags": []}})
@patch("subprocess.run")
def test_shell(mock_run, _, input): def test_shell(mock_run, _, input):
mock_run.reset_mock() mock_run.reset_mock()
args = ['shell', 'repo7', input] args = ["shell", "repo7", input]
__main__.main(args) __main__.main(args)
expected_cmds = input expected_cmds = input
mock_run.assert_called_once_with(expected_cmds, cwd='path7', shell=True, stderr=-2, stdout=-1) mock_run.assert_called_once_with(
expected_cmds, cwd="path7", shell=True, stderr=-2, stdout=-1
)
class TestContext: class TestContext:
@patch("gita.utils.get_context", return_value=None)
@patch('gita.utils.get_context', return_value=None)
def test_display_no_context(self, _, capfd): def test_display_no_context(self, _, capfd):
__main__.main(['context']) __main__.main(["context"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'Context is not set\n' == out assert "Context is not set\n" == out
@patch('gita.utils.get_context', return_value=Path('gname.context')) @patch("gita.utils.get_context", return_value=Path("gname.context"))
@patch('gita.utils.get_groups', return_value={'gname': {'repos': ['a', 'b']}}) @patch("gita.utils.get_groups", return_value={"gname": {"repos": ["a", "b"]}})
def test_display_context(self, _, __, capfd): def test_display_context(self, _, __, capfd):
__main__.main(['context']) __main__.main(["context"])
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'gname: a b\n' == out assert "gname: a b\n" == out
@patch('gita.utils.get_context') @patch("gita.utils.get_context")
def test_reset(self, mock_ctx): def test_reset(self, mock_ctx):
__main__.main(['context', 'none']) __main__.main(["context", "none"])
mock_ctx.return_value.unlink.assert_called() mock_ctx.return_value.unlink.assert_called()
@patch('gita.utils.get_context', return_value=None) @patch("gita.utils.get_context", return_value=None)
@patch('gita.common.get_config_dir', return_value=TEST_DIR) @patch("gita.common.get_config_dir", return_value=TEST_DIR)
@patch('gita.utils.get_groups', return_value={'lala': ['b'], 'kaka': []}) @patch("gita.utils.get_groups", return_value={"lala": ["b"], "kaka": []})
def test_set_first_time(self, *_): def test_set_first_time(self, *_):
ctx = TEST_DIR / 'lala.context' ctx = TEST_DIR / "lala.context"
assert not ctx.is_file() assert not ctx.is_file()
__main__.main(['context', 'lala']) __main__.main(["context", "lala"])
assert ctx.is_file() assert ctx.is_file()
ctx.unlink() ctx.unlink()
@patch('gita.common.get_config_dir', return_value=TEST_DIR) @patch("gita.common.get_config_dir", return_value=TEST_DIR)
@patch('gita.utils.get_groups', return_value={'lala': ['b'], 'kaka': []}) @patch("gita.utils.get_groups", return_value={"lala": ["b"], "kaka": []})
@patch('gita.utils.get_context') @patch("gita.utils.get_context")
def test_set_second_time(self, mock_ctx, *_): def test_set_second_time(self, mock_ctx, *_):
__main__.main(['context', 'kaka']) __main__.main(["context", "kaka"])
mock_ctx.return_value.rename.assert_called() mock_ctx.return_value.rename.assert_called()
class TestGroupCmd: class TestGroupCmd:
@patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
def test_ls(self, _, capfd): def test_ls(self, _, capfd):
args = argparse.Namespace() args = argparse.Namespace()
args.to_group = None args.to_group = None
args.group_cmd = 'ls' args.group_cmd = "ls"
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'xx yy\n' == out assert "xx yy\n" == out
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
def test_ll(self, _, capfd): def test_ll(self, _, capfd):
args = argparse.Namespace() args = argparse.Namespace()
args.to_group = None args.to_group = None
@ -321,175 +376,228 @@ class TestGroupCmd:
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert out == '\x1b[4mxx\x1b[0m: \n - a\n - b\n\x1b[4myy\x1b[0m: \n - a\n - c\n - d\n' assert (
out
== "\x1b[4mxx\x1b[0m: \n - a\n - b\n\x1b[4myy\x1b[0m: \n - a\n - c\n - d\n"
)
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
def test_ll_with_group(self, _, capfd): def test_ll_with_group(self, _, capfd):
args = argparse.Namespace() args = argparse.Namespace()
args.to_group = None args.to_group = None
args.group_cmd = None args.group_cmd = None
args.to_show = 'yy' args.to_show = "yy"
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'a c d\n' == out assert "a c d\n" == out
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
@patch('gita.utils.write_to_groups_file') @patch("gita.utils.write_to_groups_file")
def test_rename(self, mock_write, _): def test_rename(self, mock_write, _):
args = argparse.Namespace() args = argparse.Namespace()
args.gname = 'xx' args.gname = "xx"
args.new_name = 'zz' args.new_name = "zz"
args.group_cmd = 'rename' args.group_cmd = "rename"
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
expected = {'yy': {'repos': ['a', 'c', 'd'], 'path': ''}, expected = {
'zz': {'repos': ['a', 'b'], 'path': ''}} "yy": {"repos": ["a", "c", "d"], "path": ""},
mock_write.assert_called_once_with(expected, 'w') "zz": {"repos": ["a", "b"], "path": ""},
}
mock_write.assert_called_once_with(expected, "w")
@patch('gita.info.get_color_encoding', return_value=info.default_colors) @patch("gita.info.get_color_encoding", return_value=info.default_colors)
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
def test_rename_error(self, *_): def test_rename_error(self, *_):
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
with pytest.raises(SystemExit, match="1"): with pytest.raises(SystemExit, match="1"):
__main__.main('group rename xx yy'.split()) __main__.main("group rename xx yy".split())
@pytest.mark.parametrize('input, expected', [ @pytest.mark.parametrize(
('xx', {'yy': {'repos': ['a', 'c', 'd'], 'path': ''}}), "input, expected",
[
("xx", {"yy": {"repos": ["a", "c", "d"], "path": ""}}),
("xx yy", {}), ("xx yy", {}),
]) ],
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''}) )
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.utils.get_repos", return_value={"a": "", "b": "", "c": "", "d": ""})
@patch('gita.utils.write_to_groups_file') @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
@patch("gita.utils.write_to_groups_file")
def test_rm(self, mock_write, _, __, input, expected): def test_rm(self, mock_write, _, __, input, expected):
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
args = ['group', 'rm'] + shlex.split(input) args = ["group", "rm"] + shlex.split(input)
__main__.main(args) __main__.main(args)
mock_write.assert_called_once_with(expected, 'w') mock_write.assert_called_once_with(expected, "w")
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''}) @patch("gita.utils.get_repos", return_value={"a": "", "b": "", "c": "", "d": ""})
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
@patch('gita.utils.write_to_groups_file') @patch("gita.utils.write_to_groups_file")
def test_add(self, mock_write, *_): def test_add(self, mock_write, *_):
args = argparse.Namespace() args = argparse.Namespace()
args.to_group = ['a', 'c'] args.to_group = ["a", "c"]
args.group_cmd = 'add' args.group_cmd = "add"
args.gname = 'zz' args.gname = "zz"
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
mock_write.assert_called_once_with( mock_write.assert_called_once_with(
{'zz': {'repos': ['a', 'c'], 'path': ''}}, 'a+') {"zz": {"repos": ["a", "c"], "path": ""}}, "a+"
)
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''}) @patch("gita.utils.get_repos", return_value={"a": "", "b": "", "c": "", "d": ""})
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
@patch('gita.utils.write_to_groups_file') @patch("gita.utils.write_to_groups_file")
def test_add_to_existing(self, mock_write, *_): def test_add_to_existing(self, mock_write, *_):
args = argparse.Namespace() args = argparse.Namespace()
args.to_group = ['a', 'c'] args.to_group = ["a", "c"]
args.group_cmd = 'add' args.group_cmd = "add"
args.gname = 'xx' args.gname = "xx"
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
mock_write.assert_called_once_with( mock_write.assert_called_once_with(
{'xx': {'repos': ['a', 'b', 'c'], 'path': ''}, {
'yy': {'repos': ['a', 'c', 'd'], 'path': ''}}, 'w') "xx": {"repos": ["a", "b", "c"], "path": ""},
"yy": {"repos": ["a", "c", "d"], "path": ""},
},
"w",
)
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''}) @patch("gita.utils.get_repos", return_value={"a": "", "b": "", "c": "", "d": ""})
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME) @patch("gita.common.get_config_fname", return_value=GROUP_FNAME)
@patch('gita.utils.write_to_groups_file') @patch("gita.utils.write_to_groups_file")
def test_rm_repo(self, mock_write, *_): def test_rm_repo(self, mock_write, *_):
args = argparse.Namespace() args = argparse.Namespace()
args.to_rm = ['a', 'c'] args.to_rm = ["a", "c"]
args.group_cmd = 'rmrepo' args.group_cmd = "rmrepo"
args.gname = 'xx' args.gname = "xx"
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.f_group(args) __main__.f_group(args)
mock_write.assert_called_once_with( mock_write.assert_called_once_with(
{'xx': {'repos': ['b'], 'path': ''}, {
'yy': {'repos': ['a', 'c', 'd'], 'path': ''}}, 'w') "xx": {"repos": ["b"], "path": ""},
"yy": {"repos": ["a", "c", "d"], "path": ""},
},
"w",
)
@patch('gita.common.get_config_fname') @patch("gita.common.get_config_fname")
def test_integration(self, mock_path_fname, tmp_path, capfd): def test_integration(self, mock_path_fname, tmp_path, capfd):
def side_effect(input, _=None): def side_effect(input, _=None):
return tmp_path / f'{input}.csv' return tmp_path / f"{input}.csv"
mock_path_fname.side_effect = side_effect mock_path_fname.side_effect = side_effect
__main__.main('add .'.split()) __main__.main("add .".split())
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
__main__.main('group add gita -n test'.split()) __main__.main("group add gita -n test".split())
utils.get_groups.cache_clear() utils.get_groups.cache_clear()
__main__.main('ll test'.split()) __main__.main("ll test".split())
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert err == '' assert err == ""
assert 'gita' in out assert "gita" in out
@patch('gita.utils.is_git', return_value=True) @patch("gita.utils.is_git", return_value=True)
@patch('gita.common.get_config_fname', return_value=PATH_FNAME) @patch("gita.common.get_config_fname", return_value=PATH_FNAME)
@patch('gita.utils.rename_repo') @patch("gita.utils.rename_repo")
def test_rename(mock_rename, _, __): def test_rename(mock_rename, _, __):
utils.get_repos.cache_clear() utils.get_repos.cache_clear()
args = ['rename', 'repo1', 'abc'] args = ["rename", "repo1", "abc"]
__main__.main(args) __main__.main(args)
mock_rename.assert_called_once_with( mock_rename.assert_called_once_with(
{'repo1': {'path': '/a/bcd/repo1', 'type': '', 'flags': []}, {
'xxx': {'path': '/a/b/c/repo3', 'type': '', 'flags': []}, "repo1": {"path": "/a/bcd/repo1", "type": "", "flags": []},
'repo2': {'path': '/e/fgh/repo2', 'type': '', 'flags': []}}, "xxx": {"path": "/a/b/c/repo3", "type": "", "flags": []},
'repo1', 'abc') "repo2": {"path": "/e/fgh/repo2", "type": "", "flags": []},
},
"repo1",
"abc",
)
class TestInfo: class TestInfo:
@patch("gita.common.get_config_fname", return_value="")
@patch('gita.common.get_config_fname', return_value='')
def test_ll(self, _, capfd): def test_ll(self, _, capfd):
args = argparse.Namespace() args = argparse.Namespace()
args.info_cmd = None args.info_cmd = None
__main__.f_info(args) __main__.f_info(args)
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert 'In use: branch,commit_msg,commit_time\nUnused: path\n' == out assert "In use: branch,commit_msg,commit_time\nUnused: path\n" == out
assert err == '' assert err == ""
@patch('gita.common.get_config_fname') @patch("gita.common.get_config_fname")
def test_add(self, mock_get_fname, tmpdir): def test_add(self, mock_get_fname, tmpdir):
args = argparse.Namespace() args = argparse.Namespace()
args.info_cmd = 'add' args.info_cmd = "add"
args.info_item = 'path' args.info_item = "path"
with tmpdir.as_cwd(): with tmpdir.as_cwd():
csv_config = Path.cwd() / 'info.csv' csv_config = Path.cwd() / "info.csv"
mock_get_fname.return_value = csv_config mock_get_fname.return_value = csv_config
__main__.f_info(args) __main__.f_info(args)
items = info.get_info_items() items = info.get_info_items()
assert items == ['branch', 'commit_msg', 'commit_time', 'path'] assert items == ["branch", "commit_msg", "commit_time", "path"]
@patch('gita.common.get_config_fname') @patch("gita.common.get_config_fname")
def test_rm(self, mock_get_fname, tmpdir): def test_rm(self, mock_get_fname, tmpdir):
args = argparse.Namespace() args = argparse.Namespace()
args.info_cmd = 'rm' args.info_cmd = "rm"
args.info_item = 'commit_msg' args.info_item = "commit_msg"
with tmpdir.as_cwd(): with tmpdir.as_cwd():
csv_config = Path.cwd() / 'info.csv' csv_config = Path.cwd() / "info.csv"
mock_get_fname.return_value = csv_config mock_get_fname.return_value = csv_config
__main__.f_info(args) __main__.f_info(args)
items = info.get_info_items() items = info.get_info_items()
assert items == ['branch', 'commit_time'] assert items == ["branch", "commit_time"]
@patch('gita.common.get_config_fname') @patch("gita.common.get_config_fname")
def test_set_color(mock_get_fname, tmpdir): def test_set_color(mock_get_fname, tmpdir):
args = argparse.Namespace() args = argparse.Namespace()
args.color_cmd = 'set' args.color_cmd = "set"
args.color = 'b_white' args.color = "b_white"
args.situation = 'no-remote' args.situation = "no-remote"
with tmpdir.as_cwd(): with tmpdir.as_cwd():
csv_config = Path.cwd() / 'colors.csv' csv_config = Path.cwd() / "colors.csv"
mock_get_fname.return_value = csv_config mock_get_fname.return_value = csv_config
__main__.f_color(args) __main__.f_color(args)
info.get_color_encoding.cache_clear() # avoid side effect info.get_color_encoding.cache_clear() # avoid side effect
items = info.get_color_encoding() items = info.get_color_encoding()
info.get_color_encoding.cache_clear() # avoid side effect info.get_color_encoding.cache_clear() # avoid side effect
assert items == {'no-remote': 'b_white', 'in-sync': 'green', assert items == {
'diverged': 'red', 'local-ahead': 'purple', "no-remote": "b_white",
'remote-ahead': 'yellow'} "in-sync": "green",
"diverged": "red",
"local-ahead": "purple",
"remote-ahead": "yellow",
}
@pytest.mark.parametrize(
"input, expected",
[
({"repo1": {"path": "/a/"}, "repo2": {"path": "/b/"}}, ""),
],
)
@patch("gita.utils.write_to_groups_file")
@patch("gita.utils.write_to_repo_file")
@patch("gita.utils.get_repos")
def test_clear(
mock_repos,
mock_write_to_repo_file,
mock_write_to_groups_file,
input,
expected,
capfd,
):
mock_repos.return_value = input
__main__.main(["clear"])
assert mock_write_to_repo_file.call_count == 1
mock_write_to_repo_file.assert_called_once_with({}, "w")
assert mock_write_to_groups_file.call_count == 1
mock_write_to_groups_file.assert_called_once_with({}, "w")
out, err = capfd.readouterr()
assert err == ""
assert out == expected