Initial commit (0.1.1)
This commit is contained in:
commit
7557526117
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
dist
|
||||
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Git Overview - Work In Progress
|
||||
|
||||
Displays a brief summary for multiple git work directories.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ git-overview -a ~/Projects
|
||||
Workdir Branch Status
|
||||
\033[93mnoted main changed [1M]\03[0m
|
||||
git-overview main untracked [6U]
|
||||
xmon code-revie untracked [3U]
|
||||
polyphonic-forms native-for changed [7M, 1D, 3U]
|
||||
polyphonic master changed [1M, 1U]
|
||||
django-byostorage master clean
|
||||
```
|
||||
128
git_overview.py
Executable file
128
git_overview.py
Executable file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import dataclasses
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
STATUS_COLORS = {
|
||||
"untracked": 93,
|
||||
"changed": 31,
|
||||
"clean": 32,
|
||||
"unknown": 94,
|
||||
}
|
||||
|
||||
FORMAT_STRINGS = {
|
||||
"default": "\033[{color}m{name:20.20s} {branch:10.10s} {status:10.10s} {changes}\033[0m"
|
||||
}
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class GitStatus:
|
||||
name: str
|
||||
branch: str = ""
|
||||
status: str = "untracked"
|
||||
modified: int = 0
|
||||
deleted: int = 0
|
||||
untracked: int = 0
|
||||
|
||||
def info(self, fmt):
|
||||
changes = []
|
||||
if self.modified:
|
||||
changes.append(f"{self.modified}M")
|
||||
if self.deleted:
|
||||
changes.append(f"{self.deleted}D")
|
||||
if self.untracked:
|
||||
changes.append(f"{self.untracked}U")
|
||||
|
||||
changes = f" [{', '.join(changes)}]" if changes else ""
|
||||
color = STATUS_COLORS.get(self.status, STATUS_COLORS["unknown"])
|
||||
fmt = fmt or FORMAT_STRINGS["default"]
|
||||
|
||||
return fmt.format(changes=changes, color=color, **dataclasses.asdict(self))
|
||||
|
||||
|
||||
def multi_repo_overview(directory: str, all: bool = False):
|
||||
result = []
|
||||
for f in os.scandir(directory):
|
||||
if not f.is_dir():
|
||||
continue
|
||||
|
||||
try:
|
||||
result.append(repo_overview(f.path))
|
||||
except FileNotFoundError:
|
||||
if all:
|
||||
result.append(GitStatus(name=f.name, status="-"))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def repo_overview(repo):
|
||||
if not os.path.isdir(os.path.join(repo, ".git")):
|
||||
raise FileNotFoundError(f"{repo} is not a git repo")
|
||||
|
||||
p = subprocess.run(("git", "-C", repo, "status"), capture_output=True)
|
||||
lines = p.stdout.decode().split("\n")
|
||||
|
||||
info = GitStatus(name=os.path.basename(repo))
|
||||
for line in lines:
|
||||
if line.startswith("On branch"):
|
||||
info.branch = line[10:]
|
||||
|
||||
if line.startswith("Changes "):
|
||||
info.status = "changed"
|
||||
|
||||
if line.startswith("Your branch is up to date"):
|
||||
info.status = "clean"
|
||||
|
||||
if line.startswith("\tmodified:"):
|
||||
info.modified += 1
|
||||
elif line.startswith("\tdeleted:"):
|
||||
info.deleted += 1
|
||||
elif line.startswith("\t"):
|
||||
info.untracked += 1
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def cmd():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check the work trees in a directory for their status"
|
||||
)
|
||||
parser.add_argument("--log-level", "-l", default="info")
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
"-a",
|
||||
action="store_true",
|
||||
help="Include directories without a git folder",
|
||||
)
|
||||
parser.add_argument("--format", "-f", default="default")
|
||||
parser.add_argument("directory", nargs="?", default=".")
|
||||
options = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=getattr(logging, options.log_level.upper(), logging.INFO))
|
||||
logger.debug(options)
|
||||
|
||||
repos = multi_repo_overview(options.directory, options.all)
|
||||
|
||||
fmt = FORMAT_STRINGS.get(options.format, FORMAT_STRINGS["default"])
|
||||
|
||||
print(
|
||||
fmt.format(
|
||||
color=0,
|
||||
name="Workdir",
|
||||
branch="Branch",
|
||||
status="Status",
|
||||
changes="",
|
||||
)
|
||||
)
|
||||
for repo in repos:
|
||||
print(repo.info(fmt))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd()
|
||||
7
poetry.lock
generated
Normal file
7
poetry.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand.
|
||||
package = []
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10"
|
||||
content-hash = "b08227dc5674045d1a6b5ab20f461c6a08d2d9bdfd724891aceb1a0c2b3614b1"
|
||||
19
pyproject.toml
Normal file
19
pyproject.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[project]
|
||||
name = "git-overview"
|
||||
version = "0.1.1"
|
||||
description = "Show overview of multiple git working directories\""
|
||||
authors = [
|
||||
{name = "Tris Forster",email = "tris@tfconsulting.com.au"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.0"
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
git-overview = "git_overview:cmd"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
Loading…
x
Reference in New Issue
Block a user