Merging upstream version 0.16.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
761a845342
commit
6cfcc808bb
14 changed files with 83 additions and 889 deletions
|
@ -59,34 +59,24 @@ def f_add(args: argparse.Namespace):
|
|||
repos = utils.get_repos()
|
||||
paths = args.paths
|
||||
groups = utils.get_groups()
|
||||
if 0:
|
||||
# add to global and tag as main
|
||||
main_repos = utils.add_repos(repos, paths, repo_type='m')
|
||||
# add sub-repo recursively and save to local config
|
||||
for name, prop in main_repos.items():
|
||||
main_path = prop['path']
|
||||
print('Inside main repo:', name)
|
||||
#sub_paths = Path(main_path).glob('**')
|
||||
sub_paths = glob.glob(os.path.join(main_path,'**/'), recursive=True)
|
||||
utils.add_repos({}, sub_paths, root=main_path)
|
||||
else:
|
||||
if args.recursive or args.auto_group:
|
||||
paths = (p.rstrip(os.path.sep) for p in chain.from_iterable(
|
||||
glob.glob(os.path.join(p, '**/'), recursive=True)
|
||||
for p in args.paths))
|
||||
new_repos = utils.add_repos(repos, paths, is_bare=args.bare)
|
||||
if new_repos and args.auto_group:
|
||||
new_groups = utils.auto_group(new_repos, args.paths)
|
||||
if new_groups:
|
||||
print(f'Created {len(new_groups)} new group(s).')
|
||||
utils.write_to_groups_file(new_groups, 'a+')
|
||||
if new_repos and args.group:
|
||||
gname = args.group
|
||||
gname_repos = set(groups[gname]['repos'])
|
||||
gname_repos.update(new_repos)
|
||||
groups[gname]['repos'] = sorted(gname_repos)
|
||||
print(f'Added {len(new_repos)} repos to the {gname} group')
|
||||
utils.write_to_groups_file(groups, 'w')
|
||||
if args.recursive or args.auto_group:
|
||||
paths = (p.rstrip(os.path.sep) for p in chain.from_iterable(
|
||||
glob.glob(os.path.join(p, '**/'), recursive=True)
|
||||
for p in args.paths))
|
||||
new_repos = utils.add_repos(repos, paths, include_bare=args.bare,
|
||||
exclude_submodule=args.skip_submodule)
|
||||
if new_repos and args.auto_group:
|
||||
new_groups = utils.auto_group(new_repos, args.paths)
|
||||
if new_groups:
|
||||
print(f'Created {len(new_groups)} new group(s).')
|
||||
utils.write_to_groups_file(new_groups, 'a+')
|
||||
if new_repos and args.group:
|
||||
gname = args.group
|
||||
gname_repos = set(groups[gname]['repos'])
|
||||
gname_repos.update(new_repos)
|
||||
groups[gname]['repos'] = sorted(gname_repos)
|
||||
print(f'Added {len(new_repos)} repos to the {gname} group')
|
||||
utils.write_to_groups_file(groups, 'w')
|
||||
|
||||
|
||||
def f_rename(args: argparse.Namespace):
|
||||
|
@ -164,11 +154,11 @@ def f_freeze(_):
|
|||
seen = {''}
|
||||
for name, prop in repos.items():
|
||||
path = prop['path']
|
||||
# TODO: What do we do with main repos? Maybe give an option to print
|
||||
# their sub-repos too.
|
||||
url = ''
|
||||
cp = subprocess.run(['git', 'remote', '-v'], cwd=path, capture_output=True)
|
||||
lines = cp.stdout.decode('utf-8').split('\n')
|
||||
# FIXME: capture_output is new in 3.7. Maybe drop support for 3.6
|
||||
cp = subprocess.run(['git', 'remote', '-v'], cwd=path,
|
||||
universal_newlines=True, capture_output=True)
|
||||
lines = cp.stdout.split('\n')
|
||||
if cp.returncode == 0 and len(lines) > 0:
|
||||
parts = lines[0].split()
|
||||
if len(parts)>1:
|
||||
|
@ -402,6 +392,8 @@ def main(argv=None):
|
|||
p_add.add_argument('-g','--group',
|
||||
choices=utils.get_groups(),
|
||||
help="add repo(s) to the specified group")
|
||||
p_add.add_argument('-s', '--skip-submodule', action='store_true',
|
||||
help="skip submodule repo(s)")
|
||||
xgroup = p_add.add_mutually_exclusive_group()
|
||||
xgroup.add_argument('-r', '--recursive', action='store_true',
|
||||
help="recursively add repo(s) in the given path(s).")
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import os
|
||||
|
||||
|
||||
def get_config_dir(root=None) -> str:
|
||||
if root is None:
|
||||
root = os.environ.get('XDG_CONFIG_HOME') or os.path.join(
|
||||
os.path.expanduser('~'), '.config')
|
||||
return os.path.join(root, "gita")
|
||||
else:
|
||||
return os.path.join(root, ".gita")
|
||||
def get_config_dir() -> str:
|
||||
root = os.environ.get('XDG_CONFIG_HOME') or os.path.join(
|
||||
os.path.expanduser('~'), '.config')
|
||||
return os.path.join(root, "gita")
|
||||
|
||||
|
||||
def get_config_fname(fname: str, root=None) -> str:
|
||||
def get_config_fname(fname: str) -> str:
|
||||
"""
|
||||
Return the file name that stores the repo locations.
|
||||
"""
|
||||
return os.path.join(get_config_dir(root), fname)
|
||||
return os.path.join(get_config_dir(), fname)
|
||||
|
|
100
gita/utils.py
100
gita/utils.py
|
@ -38,14 +38,12 @@ def get_relative_path(kid: str, parent: str) -> Union[List[str], None]:
|
|||
|
||||
|
||||
@lru_cache()
|
||||
def get_repos(root=None) -> Dict[str, Dict[str, str]]:
|
||||
def get_repos() -> Dict[str, Dict[str, str]]:
|
||||
"""
|
||||
Return a `dict` of repo name to repo absolute path and repo type
|
||||
|
||||
@param root: Use local config if set. If None, use either global or local
|
||||
config depending on cwd.
|
||||
"""
|
||||
path_file = common.get_config_fname('repos.csv', root)
|
||||
path_file = common.get_config_fname('repos.csv')
|
||||
repos = {}
|
||||
if os.path.isfile(path_file) and os.stat(path_file).st_size > 0:
|
||||
with open(path_file) as f:
|
||||
|
@ -54,13 +52,7 @@ def get_repos(root=None) -> Dict[str, Dict[str, str]]:
|
|||
repos = {r['name']:
|
||||
{'path': r['path'], 'type': r['type'],
|
||||
'flags': r['flags'].split()}
|
||||
for r in rows if is_git(r['path'], is_bare=True)}
|
||||
if root is None: # detect if inside a main path
|
||||
cwd = os.getcwd()
|
||||
for prop in repos.values():
|
||||
path = prop['path']
|
||||
if prop['type'] == 'm' and get_relative_path(cwd, path) != MAX_INT:
|
||||
return get_repos(path)
|
||||
for r in rows if is_git(r['path'], include_bare=True)}
|
||||
return repos
|
||||
|
||||
|
||||
|
@ -82,7 +74,6 @@ def get_context() -> Union[Path, None]:
|
|||
ctx = matches[0]
|
||||
if ctx.stem == 'auto':
|
||||
cwd = str(Path.cwd())
|
||||
repos = get_repos()
|
||||
# The context is set to be the group with minimal distance to cwd
|
||||
candidate = None
|
||||
min_dist = MAX_INT
|
||||
|
@ -102,7 +93,7 @@ def get_context() -> Union[Path, None]:
|
|||
|
||||
|
||||
@lru_cache()
|
||||
def get_groups() -> Dict[str, Dict]:
|
||||
def get_groups() -> Dict[str, Dict[str, Union[str, List]]]:
|
||||
"""
|
||||
Return a `dict` of group name to group properties such as repo names and
|
||||
group path.
|
||||
|
@ -152,7 +143,7 @@ def replace_context(old: Union[Path, None], new: str):
|
|||
# ctx.rename(ctx.with_stem(new_name)) # only works in py3.9
|
||||
old.rename(old.with_name(f'{new}.context'))
|
||||
else:
|
||||
open(auto.with_name(f'{new}.context'), 'w').close()
|
||||
Path(auto.with_name(f'{new}.context')).write_text('')
|
||||
|
||||
|
||||
def get_choices() -> List[Union[str, None]]:
|
||||
|
@ -170,7 +161,16 @@ def get_choices() -> List[Union[str, None]]:
|
|||
return choices
|
||||
|
||||
|
||||
def is_git(path: str, is_bare=False) -> bool:
|
||||
def is_submodule_repo(p: Path) -> bool:
|
||||
"""
|
||||
|
||||
"""
|
||||
if p.is_file() and '.git/modules' in p.read_text():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_git(path: str, include_bare=False, exclude_submodule=False) -> bool:
|
||||
"""
|
||||
Return True if the path is a git repo.
|
||||
"""
|
||||
|
@ -178,16 +178,18 @@ def is_git(path: str, is_bare=False) -> bool:
|
|||
return False
|
||||
# An alternative is to call `git rev-parse --is-inside-work-tree`
|
||||
# I don't see why that one is better yet.
|
||||
# For a regular git repo, .git is a folder, for a worktree repo, .git is a file.
|
||||
# However, git submodule repo also has .git as a file.
|
||||
# For a regular git repo, .git is a folder. For a worktree repo and
|
||||
# submodule repo, .git is a file.
|
||||
# A more reliable way to differentiable regular and worktree repos is to
|
||||
# compare the result of `git rev-parse --git-dir` and
|
||||
# `git rev-parse --git-common-dir`
|
||||
loc = os.path.join(path, '.git')
|
||||
# TODO: we can display the worktree repos in a different font.
|
||||
if os.path.exists(loc):
|
||||
if exclude_submodule and is_submodule_repo(Path(loc)):
|
||||
return False
|
||||
return True
|
||||
if not is_bare:
|
||||
if not include_bare:
|
||||
return False
|
||||
# detect bare repo
|
||||
got = subprocess.run('git rev-parse --is-bare-repository'.split(),
|
||||
|
@ -209,37 +211,26 @@ def rename_repo(repos: Dict[str, Dict[str, str]], repo: str, new_name: str):
|
|||
prop = repos[repo]
|
||||
del repos[repo]
|
||||
repos[new_name] = prop
|
||||
# write to local config if inside a main path
|
||||
main_paths = (prop['path'] for prop in repos.values() if prop['type'] == 'm')
|
||||
cwd = os.getcwd()
|
||||
is_local_config = True
|
||||
# TODO: delete
|
||||
for p in main_paths:
|
||||
if get_relative_path(cwd, p) != MAX_INT:
|
||||
write_to_repo_file(repos, 'w', p)
|
||||
break
|
||||
else: # global config
|
||||
write_to_repo_file(repos, 'w')
|
||||
is_local_config = False
|
||||
# update groups only when outside any main repos
|
||||
if is_local_config:
|
||||
return
|
||||
write_to_repo_file(repos, 'w')
|
||||
|
||||
groups = get_groups()
|
||||
for g, members in groups.items():
|
||||
for g, values in groups.items():
|
||||
members = values['repos']
|
||||
if repo in members:
|
||||
members.remove(repo)
|
||||
members.append(new_name)
|
||||
groups[g] = sorted(members)
|
||||
groups[g]['repos'] = sorted(members)
|
||||
write_to_groups_file(groups, 'w')
|
||||
|
||||
|
||||
def write_to_repo_file(repos: Dict[str, Dict[str, str]], mode: str, root=None):
|
||||
def write_to_repo_file(repos: Dict[str, Dict[str, str]], mode: str):
|
||||
"""
|
||||
@param repos: each repo is {name: {properties}}
|
||||
"""
|
||||
data = [(prop['path'], name, prop['type'], ' '.join(prop['flags']))
|
||||
# The 3rd column is repo type; unused field
|
||||
data = [(prop['path'], name, '', ' '.join(prop['flags']))
|
||||
for name, prop in repos.items()]
|
||||
fname = common.get_config_fname('repos.csv', root)
|
||||
fname = common.get_config_fname('repos.csv')
|
||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||
with open(fname, mode, newline='') as f:
|
||||
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
||||
|
@ -254,7 +245,7 @@ def write_to_groups_file(groups: Dict[str, Dict], mode: str):
|
|||
fname = common.get_config_fname('groups.csv')
|
||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||
if not groups: # all groups are deleted
|
||||
open(fname, 'w').close()
|
||||
Path(fname).write_text('')
|
||||
else:
|
||||
# delete the group if there are no repos
|
||||
for name in list(groups):
|
||||
|
@ -285,27 +276,17 @@ def _make_name(path: str, repos: Dict[str, Dict[str, str]],
|
|||
return name
|
||||
|
||||
|
||||
# TODO: delete
|
||||
def _get_repo_type(path, repo_type, root) -> str:
|
||||
"""
|
||||
|
||||
"""
|
||||
if repo_type != '': # explicitly set
|
||||
return repo_type
|
||||
if root is not None and os.path.normpath(root) == os.path.normpath(path):
|
||||
return 'm'
|
||||
return ''
|
||||
|
||||
|
||||
def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str],
|
||||
repo_type='', root=None, is_bare=False) -> Dict[str, Dict[str, str]]:
|
||||
include_bare=False,
|
||||
exclude_submodule=False,
|
||||
) -> Dict[str, Dict[str, str]]:
|
||||
"""
|
||||
Write new repo paths to file; return the added repos.
|
||||
|
||||
@param repos: name -> path
|
||||
"""
|
||||
existing_paths = {prop['path'] for prop in repos.values()}
|
||||
new_paths = {p for p in new_paths if is_git(p, is_bare)}
|
||||
new_paths = {p for p in new_paths if is_git(p, include_bare, exclude_submodule)}
|
||||
new_paths = new_paths - existing_paths
|
||||
new_repos = {}
|
||||
if new_paths:
|
||||
|
@ -315,12 +296,9 @@ def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str],
|
|||
)
|
||||
new_repos = {_make_name(path, repos, name_counts): {
|
||||
'path': path,
|
||||
'type': _get_repo_type(path, repo_type, root),
|
||||
'flags': '',
|
||||
} for path in new_paths}
|
||||
# When root is not None, we could optionally set its type to 'm', i.e.,
|
||||
# main repo.
|
||||
write_to_repo_file(new_repos, 'a+', root)
|
||||
write_to_repo_file(new_repos, 'a+')
|
||||
else:
|
||||
print('No new repos found!')
|
||||
return new_repos
|
||||
|
@ -442,13 +420,7 @@ def describe(repos: Dict[str, Dict[str, str]], no_colors: bool = False) -> str:
|
|||
|
||||
for name in sorted(repos):
|
||||
info_items = ' '.join(f(repos[name]) for f in funcs)
|
||||
if repos[name]['type'] == 'm':
|
||||
# ANSI color code also takes length in Python
|
||||
name = f'{info.Color.underline}{name}{info.Color.end}'
|
||||
width = name_width + 8
|
||||
yield f'{name:<{width}}{info_items}'
|
||||
else:
|
||||
yield f'{name:<{name_width}}{info_items}'
|
||||
yield f'{name:<{name_width}}{info_items}'
|
||||
|
||||
|
||||
def get_cmds_from_files() -> Dict[str, Dict[str, str]]:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue