Skip to content

Commit

Permalink
Merge pull request #62 from nosarthur/rename
Browse files Browse the repository at this point in the history
add rename sub-command
  • Loading branch information
nosarthur authored Jun 14, 2019
2 parents c521179 + 2684120 commit 57cb601
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 34 deletions.
23 changes: 20 additions & 3 deletions gita/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@


def f_add(args: argparse.Namespace):
utils.add_repos(args.paths)
repos = utils.get_repos()
utils.add_repos(repos, args.paths)


def f_rename(args: argparse.Namespace):
repos = utils.get_repos()
utils.rename_repo(repos, args.repo[0], args.new_name)


def f_ll(args: argparse.Namespace):
Expand Down Expand Up @@ -123,6 +129,17 @@ def main(argv=None):
help="remove the chosen repo(s)")
p_rm.set_defaults(func=f_rm)

p_rename = subparsers.add_parser('rename', help='rename a repo')
p_rename.add_argument(
'repo',
nargs=1,
choices=utils.get_repos(),
help="rename the chosen repo")
p_rename.add_argument(
'new_name',
help="new name")
p_rename.set_defaults(func=f_rename)

ll_doc = f''' status symbols:
+: staged changes
*: unstaged changes
Expand Down Expand Up @@ -153,8 +170,8 @@ def main(argv=None):
# superman mode
p_super = subparsers.add_parser(
'super',
help=
'superman mode: delegate any git command/alias in specified or all repo(s).\n'
help='superman mode: delegate any git command/alias in specified or '
'all repo(s).\n'
'Examples:\n \t gita super myrepo1 commit -am "fix a bug"\n'
'\t gita super repo1 repo2 repo3 checkout new-feature')
p_super.add_argument(
Expand Down
59 changes: 43 additions & 16 deletions gita/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,27 @@ def get_repos() -> Dict[str, str]:
Return a `dict` of repo name to repo absolute path
"""
path_file = get_path_fname()
paths = []
data = []
if os.path.isfile(path_file) and os.stat(path_file).st_size > 0:
with open(path_file) as f:
paths = f.read().splitlines()[0].split(os.pathsep)
data = ((os.path.basename(os.path.normpath(p)), p) for p in paths
if is_git(p))
# TODO: read lines one by one
# for line in f:
data = f.read().splitlines()
# The file content is repos separated by :
# For each repo, there are path and repo name separated by ,
# If repo name is not provided, the basename of the path is used as name.
# For example, "/a/b/c,xx:/a/b/d:/c/e/f" corresponds to
# {xx: /a/b/c, d: /a/b/d, f: /c/e/f}
repos = {}
for name, path in data:
for d in data:
if not d: # blank line
continue
path, name = d.split(',')
if not is_git(path):
continue
if name not in repos:
repos[name] = path
else:
else: # repo name collision for different paths: include parent path name
par_name = os.path.basename(os.path.dirname(path))
repos[os.path.join(par_name, name)] = path
return repos
Expand Down Expand Up @@ -79,22 +89,39 @@ def is_git(path: str) -> bool:
return os.path.exists(loc)


def add_repos(new_paths: List[str]):
def rename_repo(repos: Dict[str, str], repo: str, new_name: str):
"""
Write new repo name to file
"""
path = repos[repo]
del repos[repo]
repos[new_name] = path
_write_to_repo_file(repos, 'w')


def _write_to_repo_file(repos: Dict[str, str], mode: str):
"""
"""
data = ''.join(f'{path},{name}\n' for name, path in repos.items())
fname = get_path_fname()
os.makedirs(os.path.dirname(fname), exist_ok=True)
with open(fname, mode) as f:
f.write(data)


def add_repos(repos: Dict[str, str], new_paths: List[str]):
"""
Write new repo paths to file
"""
existing_paths = set(get_repos().values())
existing_paths = set(repos.values())
new_paths = set(os.path.abspath(p) for p in new_paths if is_git(p))
new_paths = new_paths - existing_paths
if new_paths:
print(f"Found {len(new_paths)} new repo(s):")
for path in new_paths:
print(path)
existing_paths.update(new_paths)
fname = get_path_fname()
os.makedirs(os.path.dirname(fname), exist_ok=True)
with open(fname, 'w') as f:
f.write(os.pathsep.join(sorted(existing_paths)))
print(f"Found {len(new_paths)} new repo(s).")
new_repos = {
os.path.basename(os.path.normpath(path)): path
for path in new_paths}
_write_to_repo_file(new_repos, 'a+')
else:
print('No new repos found!')

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pytest>=4.1.1
pytest>=4.4.0
pytest-cov>=2.6.1
pytest-xdist>=1.26.0
setuptools>=40.6.3
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
setup(
name='gita',
packages=['gita'],
version='0.9.7',
version='0.9.8',
license='MIT',
description='Manage multiple git repos',
long_description=long_description,
Expand Down
4 changes: 3 additions & 1 deletion tests/clash_path_file
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/a/bcd/repo1:/e/fgh/repo2:/root/x/repo1
/a/bcd/repo1,repo1
/e/fgh/repo2,repo2
/root/x/repo1,repo1
5 changes: 4 additions & 1 deletion tests/mock_path_file
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/a/bcd/repo1:/e/fgh/repo2
/a/bcd/repo1,repo1
/a/b/c/repo3,xxx
/e/fgh/repo2,repo2

5 changes: 3 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def testLl(self, mock_path_fname, capfd, tmp_path):
__main__.main(['add', '.'])
out, err = capfd.readouterr()
assert err == ''
assert 'Found 1 new repo(s):' in out
assert 'Found 1 new repo(s).' in out

# in production this is not needed
utils.get_repos.cache_clear()
Expand Down Expand Up @@ -52,7 +52,7 @@ def testLs(self, monkeypatch, capfd):

@pytest.mark.parametrize('path_fname, expected', [
(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, ""),
(PATH_FNAME_CLASH,
"repo1 cmaster dsu\x1b[0m msg\nrepo2 cmaster dsu\x1b[0m msg\nx/repo1 cmaster dsu\x1b[0m msg\n"
Expand All @@ -69,6 +69,7 @@ def testWithPathFiles(self, mock_path_fname, _0, _1, _2, _3, path_fname,
utils.get_repos.cache_clear()
__main__.main(['ll'])
out, err = capfd.readouterr()
print(out)
assert err == ''
assert out == expected

Expand Down
31 changes: 22 additions & 9 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def test_describe(test_input, diff_return, expected, monkeypatch):
@pytest.mark.parametrize('path_fname, expected', [
(PATH_FNAME, {
'repo1': '/a/bcd/repo1',
'repo2': '/e/fgh/repo2'
'repo2': '/e/fgh/repo2',
'xxx': '/a/b/c/repo3',
}),
(PATH_FNAME_EMPTY, {}),
(PATH_FNAME_CLASH, {
Expand Down Expand Up @@ -70,22 +71,34 @@ def test_custom_push_cmd(_):
@pytest.mark.parametrize(
'path_input, expected',
[
(['/home/some/repo/'], '/home/some/repo:/nos/repo'), # add one new
(['/home/some/repo/'], '/home/some/repo,repo\n'), # add one new
(['/home/some/repo1', '/repo2'],
'/home/some/repo1:/nos/repo:/repo2'), # add two new
{'/repo2,repo2\n/home/some/repo1,repo1\n', # add two new
'/home/some/repo1,repo1\n/repo2,repo2\n'}), # add two new
(['/home/some/repo1', '/nos/repo'],
'/home/some/repo1:/nos/repo'), # add one old one new
'/home/some/repo1,repo1\n'), # add one old one new
])
@patch('os.makedirs')
@patch('gita.utils.get_repos', return_value={'repo': '/nos/repo'})
@patch('gita.utils.is_git', return_value=True)
def test_add_repos(_0, _1, _2, path_input, expected, monkeypatch):
def test_add_repos(_0, _1, path_input, expected, monkeypatch):
monkeypatch.setenv('XDG_CONFIG_HOME', '/config')
with patch('builtins.open', mock_open()) as mock_file:
utils.add_repos(path_input)
mock_file.assert_called_with('/config/gita/repo_path', 'w')
utils.add_repos({'repo': '/nos/repo'}, path_input)
mock_file.assert_called_with('/config/gita/repo_path', 'a+')
handle = mock_file()
handle.write.assert_called_once_with(expected)
if type(expected) == str:
handle.write.assert_called_once_with(expected)
else:
handle.write.assert_called_once()
args, kwargs = handle.write.call_args
assert args[0] in expected
assert not kwargs


@patch('gita.utils._write_to_repo_file')
def test_rename_repo(mock_write):
utils.rename_repo({'r1': '/a/b', 'r2': '/c/c'}, 'r2', 'xxx')
mock_write.assert_called_once_with({'r1': '/a/b', 'xxx': '/c/c'}, 'w')


def test_async_output(capfd):
Expand Down

0 comments on commit 57cb601

Please sign in to comment.