summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--doc/README_CN.md1
-rw-r--r--gita/__main__.py101
-rw-r--r--gita/utils.py39
-rw-r--r--setup.py2
-rw-r--r--tests/test_utils.py13
6 files changed, 91 insertions, 72 deletions
diff --git a/README.md b/README.md
index 478c432..06a0141 100644
--- a/README.md
+++ b/README.md
@@ -361,6 +361,13 @@ Thus the installed git version may matter.
I have git `1.8.3.1`, `2.17.2`, and `2.20.1` on my machines, and
their results agree.
+## tips
+
+effect | shell command
+---|---
+enter `<repo>` directory|`` cd `gita ls <repo>` ``
+delete repos in `<group>` | `gita group ll <group> \| xargs gita rm`
+
## Contributing
To contribute, you can
diff --git a/doc/README_CN.md b/doc/README_CN.md
index 9666824..592ad6d 100644
--- a/doc/README_CN.md
+++ b/doc/README_CN.md
@@ -55,7 +55,6 @@
- `gita add -a <repo-parent-path(s)>`: 递归添加路径下的所有库并自动产生层级分组,见
[customization section](#custom)
- `gita add -b <bare-repo-path(s)>`: 添加bare库
-- `gita add -m <main-repo-path(s)>`: 添加main库, main库的定义见
[customization section](#custom)
- `gita add -r <repo-parent-path(s)>`: 递归添加路径下的所有库
- `gita clone <config-file>`: 克隆`<config-file>` (由`gita freeze`生成)里的库
diff --git a/gita/__main__.py b/gita/__main__.py
index d38e78d..d489f55 100644
--- a/gita/__main__.py
+++ b/gita/__main__.py
@@ -186,9 +186,9 @@ def f_ll(args: argparse.Namespace):
for line in utils.describe(repos, no_colors=args.no_colors):
print(' ', line)
else:
- for g, g_repos in utils.get_groups().items():
+ for g, prop in utils.get_groups().items():
print(f'{g}:')
- g_repos = {k: repos[k] for k in g_repos if k in repos}
+ g_repos = {k: repos[k] for k in prop['repos'] if k in repos}
for line in utils.describe(g_repos, no_colors=args.no_colors):
print(' ', line)
else:
@@ -286,26 +286,16 @@ def f_rm(args: argparse.Namespace):
path_file = common.get_config_fname('repos.csv')
if os.path.isfile(path_file):
repos = utils.get_repos()
- main_paths = [prop['path'] for prop in repos.values() if prop['type'] == 'm']
- # TODO: add test case to delete main repo from main repo
- # only local setting should be affected instead of the global one
group_updated = False
+ groups = utils.get_groups()
for repo in args.repo:
del repos[repo]
- groups = utils.get_groups()
- group_updated = group_updated or utils.delete_repo_from_groups(repo, groups)
+ up = utils.delete_repo_from_groups(repo, groups)
+ group_updated = group_updated or up
if group_updated:
utils.write_to_groups_file(groups, 'w')
- # If cwd is relative to any main repo, write to local config
- cwd = os.getcwd()
- # TODO: delete main path mechanism
- for p in main_paths:
- if utils.get_relative_path(cwd, p) is not None:
- utils.write_to_repo_file(repos, 'w', p)
- break
- else: # global config
- utils.write_to_repo_file(repos, 'w')
+ utils.write_to_repo_file(repos, 'w')
def f_git_cmd(args: argparse.Namespace):
@@ -313,20 +303,11 @@ def f_git_cmd(args: argparse.Namespace):
Delegate git command/alias defined in `args.cmd`. Asynchronous execution is
disabled for commands in the `args.async_blacklist`.
"""
- repos = utils.get_repos()
- groups = utils.get_groups()
- ctx = utils.get_context()
- if not args.repo and ctx:
- args.repo = [ctx.stem]
- if args.repo: # with user specified repo(s) or group(s)
- chosen = {}
- for k in args.repo:
- if k in repos:
- chosen[k] = repos[k]
- if k in groups:
- for r in groups[k]['repos']:
- chosen[r] = repos[r]
- repos = chosen
+ if '_parsed_repos' in args:
+ repos = args._parsed_repos
+ else:
+ repos, _ = utils.parse_repos_and_rest(args.repo)
+
per_repo_cmds = []
for prop in repos.values():
cmds = args.cmd.copy()
@@ -361,29 +342,12 @@ def f_shell(args):
Delegate shell command defined in `args.man`, which may or may not
contain repo names.
"""
- names = []
- repos = utils.get_repos()
- groups = utils.get_groups()
- ctx = utils.get_context()
- for i, word in enumerate(args.man):
- if word in repos or word in groups:
- names.append(word)
- else:
- break
- args.repo = names
- # TODO: redundant with f_git_cmd
- if not args.repo and ctx:
- args.repo = [ctx.stem]
- if args.repo: # with user specified repo(s) or group(s)
- chosen = {}
- for k in args.repo:
- if k in repos:
- chosen[k] = repos[k]
- if k in groups:
- for r in groups[k]:
- chosen[r] = repos[r]
- repos = chosen
- cmds = ' '.join(args.man[i:]) # join the shell command into a single string
+ repos, cmds = utils.parse_repos_and_rest(args.man)
+ if not cmds:
+ print('Missing commands')
+ sys.exit(2)
+
+ cmds = ' '.join(cmds) # join the shell command into a single string
for name, prop in repos.items():
# TODO: pull this out as a function
got = subprocess.run(cmds, cwd=prop['path'], shell=True,
@@ -397,16 +361,13 @@ def f_super(args):
Delegate git command/alias defined in `args.man`, which may or may not
contain repo names.
"""
- names = []
- repos = utils.get_repos()
- groups = utils.get_groups()
- for i, word in enumerate(args.man):
- if word in repos or word in groups:
- names.append(word)
- else:
- break
- args.cmd = ['git'] + args.man[i:]
- args.repo = names
+ repos, cmds = utils.parse_repos_and_rest(args.man)
+ if not cmds:
+ print('Missing commands')
+ sys.exit(2)
+
+ args.cmd = ['git'] + cmds
+ args._parsed_repos = repos
args.shell = False
f_git_cmd(args)
@@ -625,15 +586,15 @@ def main(argv=None):
p_super = subparsers.add_parser(
'super',
help='run any git command/alias',
- description='Superman mode: delegate any git command/alias in specified or '
+ description='Superman mode: delegate any git command/alias in specified repo(s), group(s), 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(
'man',
nargs=argparse.REMAINDER,
- help="execute arbitrary git command/alias for specified or all repos\n"
- "Example: gita super myrepo1 diff --name-only --staged "
+ help="execute arbitrary git command/alias for specified repo(s), group(s), or all repos.\n"
+ "Example: gita super myrepo1 diff --name-only --staged\n"
"Another: gita super checkout master ")
p_super.set_defaults(func=f_super)
@@ -641,15 +602,15 @@ def main(argv=None):
p_shell = subparsers.add_parser(
'shell',
help='run any shell command',
- description='shell mode: delegate any shell command in specified or '
+ description='shell mode: delegate any shell command in specified repo(s), group(s), or '
'all repo(s).\n'
- 'Examples:\n \t gita shell pwd\n'
+ 'Examples:\n \t gita shell pwd; \n'
'\t gita shell repo1 repo2 repo3 touch xx')
p_shell.add_argument(
'man',
nargs=argparse.REMAINDER,
- help="execute arbitrary shell command for specified or all repos "
- "Example: gita shell myrepo1 ls"
+ help="execute arbitrary shell command for specified repo(s), group(s), or all repos.\n"
+ "Example: gita shell myrepo1 ls\n"
"Another: gita shell git checkout master ")
p_shell.set_defaults(func=f_shell)
diff --git a/gita/utils.py b/gita/utils.py
index 7a1020c..2431fde 100644
--- a/gita/utils.py
+++ b/gita/utils.py
@@ -256,6 +256,10 @@ def write_to_groups_file(groups: Dict[str, Dict], mode: str):
if not groups: # all groups are deleted
open(fname, 'w').close()
else:
+ # delete the group if there are no repos
+ for name in list(groups):
+ if not groups[name]['repos']:
+ del groups[name]
with open(fname, mode, newline='') as f:
data = [
(group, ' '.join(prop['repos']), prop['path'])
@@ -475,3 +479,38 @@ def get_cmds_from_files() -> Dict[str, Dict[str, str]]:
# custom commands shadow default ones
cmds.update(custom_cmds)
return cmds
+
+
+def parse_repos_and_rest(input: List[str]
+ ) -> Tuple[Dict[str, Dict[str, str]], List[str]]:
+ """
+ Parse gita input arguments
+
+ @return: repos and the rest (e.g., gita shell and super commands)
+ """
+ i = None
+ names = []
+ repos = get_repos()
+ groups = get_groups()
+ ctx = get_context()
+ for i, word in enumerate(input):
+ if word in repos or word in groups:
+ names.append(word)
+ else:
+ break
+ else: # all input is repos and groups, shift the index once more
+ if i is not None:
+ i += 1
+ if not names and ctx:
+ names = [ctx.stem]
+ if names:
+ chosen = {}
+ for k in names:
+ if k in repos:
+ chosen[k] = repos[k]
+ if k in groups:
+ for r in groups[k]['repos']:
+ chosen[r] = repos[r]
+ # if not set here, all repos are chosen
+ repos = chosen
+ return repos, input[i:]
diff --git a/setup.py b/setup.py
index 555cf13..9941251 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ with open('README.md', encoding='utf-8') as f:
setup(
name='gita',
packages=['gita'],
- version='0.15.7',
+ version='0.15.7.3',
license='MIT',
description='Manage multiple git repos with sanity',
long_description=long_description,
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 3ff0cb1..65096e9 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -10,6 +10,19 @@ from conftest import (
)
+@pytest.mark.parametrize('input, expected', [
+ ([], ({'repo1': {'path': '/a/bcd/repo1', 'type': '', 'flags': []}, 'xxx': {'path': '/a/b/c/repo3', 'type': '', 'flags': []}, 'repo2': {'path': '/e/fgh/repo2', 'type': '', 'flags': []}}, [])),
+ (['st'], ({'repo1': {'path': '/a/bcd/repo1', 'type': '', 'flags': []}, 'xxx': {'path': '/a/b/c/repo3', 'type': '', 'flags': []}, 'repo2': {'path': '/e/fgh/repo2', 'type': '', 'flags': []}}, ['st'])),
+ (['repo1', 'st'], ({'repo1': {'flags': [], 'path': '/a/bcd/repo1', 'type': ''}}, ['st'])),
+ (['repo1'], ({'repo1': {'flags': [], 'path': '/a/bcd/repo1', 'type': ''}}, [])),
+ ])
+@patch('gita.utils.is_git', return_value=True)
+@patch('gita.common.get_config_fname', return_value=PATH_FNAME)
+def test_parse_repos_and_rest(mock_path_fname, _, input, expected):
+ got = utils.parse_repos_and_rest(input)
+ assert got == expected
+
+
@pytest.mark.parametrize('repo_path, paths, expected', [
('/a/b/c/repo', ['/a/b'], (('b', 'c'), '/a')),
])