From 648c0687ed2d863829a469e337cf556b6f25862d Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 4 Dec 2025 15:31:01 -0800 Subject: [PATCH 1/5] Use ruff, require py3.10+ --- pyproject.toml | 54 +++++++++++++++++++++++++++++++++++++++++ setup.py | 13 +++++----- src/mgit/__init__.py | 3 +-- src/mgit/cli.py | 1 - src/mgit/git.py | 33 ++++++++++++------------- tests/conftest.py | 1 - tests/test_git.py | 4 +-- tests/test_reporting.py | 10 ++++---- tox.ini | 32 +++++++----------------- 9 files changed, 93 insertions(+), 58 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1031d25 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.ruff] +cache-dir = ".tox/.ruff_cache" +line-length = 140 + +[tool.ruff.lint] +#ignore = ["RUF021", "RUF022", "RUF023", "S101"] +extend-select = [ +# "A", # flake8-builtins +# "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "C90", # mccabe +# "D", # pydocstyle + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "ERA", # eradicate + "EXE", # flake8-executable + "F", # pyflakes + "FLY", # flynt + "G", # flake8-logging-format + "I", # isort + "INT", # flake8-gettext + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PT", # flake8-pytest + "PYI", # flake8-pyi + "Q", # flake8-quotes + "RSE", # flake8-raise +# "RET", # flake8-return + "RUF", # ruff-specific +# "S", #flake8-bandit +# "SIM", # flake8-simplify +# "SLF", # flake8-self + "SLOT", # flake8-slots + "T10", # flake8-debugger + "TID", # flake8-tidy-imports + "TCH", # flake8-type-checking + "TD", # flake8-todos +# "TRY", # tryceratops + "W", # pycodestyle warnings +] + +[tool.ruff.lint.isort] +order-by-type = false + +[tool.ruff.lint.mccabe] +max-complexity = 20 + +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/setup.py b/setup.py index b9fe080..a4417f0 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,18 @@ from setuptools import setup - if __name__ == "__main__": setup( name="mgit", setup_requires="setupmeta", versioning="dev", author="Zoran Simic zoran@simicweb.com", - keywords='multiple, git, repos', url="https://github.com/zsimic/mgit", entry_points={ "console_scripts": [ "mgit = mgit.cli:main", ], }, - python_requires=">=3.6", + python_requires=">=3.10", classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", @@ -24,10 +22,11 @@ "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Build Tools", "Topic :: Utilities" diff --git a/src/mgit/__init__.py b/src/mgit/__init__.py index f44058c..2ad59dc 100644 --- a/src/mgit/__init__.py +++ b/src/mgit/__init__.py @@ -6,7 +6,6 @@ from mgit.git import GitDir, GitRunReport - LOG = logging.getLogger(__name__) @@ -79,7 +78,7 @@ def __init__(self, **kwargs): def __repr__(self): result = [self._value_representation(k) for k in sorted(self.__dict__)] - return ' '.join(s for s in result if s is not None) + return " ".join(s for s in result if s is not None) def _value_representation(self, name): value = getattr(self, name, None) diff --git a/src/mgit/cli.py b/src/mgit/cli.py index 054f0a7..153e065 100644 --- a/src/mgit/cli.py +++ b/src/mgit/cli.py @@ -18,7 +18,6 @@ from mgit import get_target, GitCheckout from mgit.git import GitRunReport - LOG = logging.getLogger(__name__) VALID_CLEAN_ACTIONS = "show local remote all reset".split() diff --git a/src/mgit/git.py b/src/mgit/git.py index da560a8..96ef7cf 100644 --- a/src/mgit/git.py +++ b/src/mgit/git.py @@ -13,7 +13,6 @@ import runez - LOG = logging.getLogger(__name__) FETCH_AGE_FILES = ["FETCH_HEAD", "HEAD"] FRESHNESS_THRESHOLD = 12 * runez.date.SECONDS_IN_ONE_HOUR @@ -621,10 +620,10 @@ def _process_line(self, line): class GitBranches(GitAspect): """Branch info""" - _command = 'branch --list --all' - _remote_prefix = 'remotes/' + _command = "branch --list --all" + _remote_prefix = "remotes/" - current = '' # Current local branch + current = "" # Current local branch local = set() # Local branches by_remote = collections.defaultdict(set) # Branches by remote (usually origin and optionally upstream) default_branches = {} # Default branch per remote @@ -632,10 +631,10 @@ class GitBranches(GitAspect): @property def shortened_current_branch(self): - return str(self.current or 'HEAD').replace('feature/', 'f/').replace('bugfix/', 'b/') + return str(self.current or "HEAD").replace("feature/", "f/").replace("bugfix/", "b/") def _process_line(self, line): - if not line or len(line) <= 3 or line[0] not in ' *' or line[1] != ' ': + if not line or len(line) <= 3 or line[0] not in " *" or line[1] != " ": LOG.warning("Internal error: malformed branch --list line: %s", line) return @@ -644,38 +643,38 @@ def _process_line(self, line): name = name[len(self._remote_prefix):] default = None try: - i = name.index(' -> ') + i = name.index(" -> ") first = name[:i] - if first.endswith('/HEAD'): + if first.endswith("/HEAD"): default = name = name[i + 4:] except ValueError: pass - remote, _, name = name.partition('/') + remote, _, name = name.partition("/") self.by_remote[remote].add(name) if default: self.default_branches[remote] = name return - if name.startswith('('): + if name.startswith("("): name = name[1:] - if name.endswith(')'): + if name.endswith(")"): name = name[:-1] - name, _, problem = name.partition(' ') - self.report.add(note='%s %s' % (name, problem)) + name, _, problem = name.partition(" ") + self.report.add(note="%s %s" % (name, problem)) self.local.add(name) - if line[0] == '*': + if line[0] == "*": self.current = name class GitConfig(GitAspect): """Remote info""" - _command = 'config --list' + _command = "config --list" origin = GitURL() # URL to remote called 'origin' remotes = {} # GitURL by remote name map tracking_remote = {} # Remotes that each local branch is tracking @@ -790,10 +789,10 @@ def _report_sorter(enum): :return int: Value to use for sorting messages in this report """ index, message = enum - if message[0] == '<': + if message[0] == "<": return -enum[0] # '<' makes message sort towards front, but keeping order with other such prefixed messages - if message[0] == '>': + if message[0] == ">": return 1000000 + enum[0] # '>' makes message sort towards end return enum[0] # Non-prefixed message stay where they were diff --git a/tests/conftest.py b/tests/conftest.py index 0b65d91..0de2ce2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,5 +2,4 @@ from mgit.cli import main - cli.default_main = main diff --git a/tests/test_git.py b/tests/test_git.py index 7d8b695..233332b 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -18,8 +18,8 @@ def test_invalid_branch_names(): def check_url(url, protocol, hostname, username, relative_path, repo, name): u = GitURL() u.set(url) - assert str(u) == (url or '') - assert u.url == (url or '') + assert str(u) == (url or "") + assert u.url == (url or "") assert u.protocol == protocol assert u.hostname == hostname assert u.username == username diff --git a/tests/test_reporting.py b/tests/test_reporting.py index ca3c1e2..694d8d0 100644 --- a/tests/test_reporting.py +++ b/tests/test_reporting.py @@ -47,14 +47,14 @@ def test_reporting(): check_sorting(GitRunReport(r1).add(problem=r2), "p1; p2; n1") # Problems come ahead of notes - check_sorting(GitRunReport().add(problem="p1").add(problem='p2').add(problem="p3"), "p1; p2; p3") - check_sorting(GitRunReport().add(problem="p1", note='n1').add(problem="p2"), "p1; p2; n1") + check_sorting(GitRunReport().add(problem="p1").add(problem="p2").add(problem="p3"), "p1; p2; p3") + check_sorting(GitRunReport().add(problem="p1", note="n1").add(problem="p2"), "p1; p2; n1") # Progress comes ahead of notes, but after problems check_sorting( GitRunReport().add( note="n1 Date: Thu, 4 Dec 2025 15:49:05 -0800 Subject: [PATCH 2/5] Updated gh actions --- .github/workflows/release.yml | 25 +++++++++++++------------ .github/workflows/tests.yml | 14 +++++++------- pyproject.toml | 6 +++--- setup.py | 2 +- src/mgit/__init__.py | 28 ++++++++++++++-------------- src/mgit/git.py | 28 ++++++++++++++-------------- tests/test_git.py | 10 ++-------- tests/test_reporting.py | 25 +++++++++---------------- 8 files changed, 63 insertions(+), 75 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4f519bf..51aeb94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,19 +9,20 @@ jobs: publish: runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # mandatory for trusted publishing steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version: '3.9' + python-version: "3.14" - - run: pip install -U pip setuptools wheel twine tox - - run: tox -e py,docs,style - - run: python setup.py sdist bdist_wheel --universal - - - name: Publish sdist and wheel - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.MGIT_TOKEN }} - run: twine upload --non-interactive dist/* + - uses: astral-sh/setup-uv@v7 + - run: uv venv + - run: uv pip install -U tox-uv + - run: .venv/bin/tox -e py,docs,style + - run: uv build + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70a8ac6..98da5d7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,17 +13,17 @@ jobs: strategy: matrix: - python-version: [3.6, 3.9] + python-version: ["3.10", "3.14"] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - run: pip install -U pip tox - run: tox -e py - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v5 with: file: .tox/test-reports/coverage.xml @@ -32,10 +32,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version: '3.9' + python-version: "3.14" - run: pip install -U pip tox - run: tox -e docs,style diff --git a/pyproject.toml b/pyproject.toml index 1031d25..c410f56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ line-length = 140 extend-select = [ # "A", # flake8-builtins # "ARG", # flake8-unused-arguments - "B", # flake8-bugbear +# "B", # flake8-bugbear "C4", # flake8-comprehensions "C90", # mccabe # "D", # pydocstyle @@ -26,12 +26,12 @@ extend-select = [ "INT", # flake8-gettext "PGH", # pygrep-hooks "PIE", # flake8-pie - "PT", # flake8-pytest +# "PT", # flake8-pytest "PYI", # flake8-pyi "Q", # flake8-quotes "RSE", # flake8-raise # "RET", # flake8-return - "RUF", # ruff-specific +# "RUF", # ruff-specific # "S", #flake8-bandit # "SIM", # flake8-simplify # "SLF", # flake8-self diff --git a/setup.py b/setup.py index a4417f0..70964f9 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,6 @@ "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Build Tools", - "Topic :: Utilities" + "Topic :: Utilities", ], ) diff --git a/src/mgit/__init__.py b/src/mgit/__init__.py index 2ad59dc..91ae18d 100644 --- a/src/mgit/__init__.py +++ b/src/mgit/__init__.py @@ -65,13 +65,13 @@ def print_modified(items, color1, color2=None): class MgitPreferences: """Various prefs""" - name_size = None # How many chars to align names when displaying list of checkouts - align = True # Whether to align names or not - verbose = False # Show verbose output - all = False # Show all entries, including missing/invalid checkout folders - fetch = False # Auto-fetch before showing status - pull = False # Auto-pull before showing status - inspect_remotes = False # Inspect remote branches to report cleanable (slower) + name_size = None # How many chars to align names when displaying list of checkouts + align = True # Whether to align names or not + verbose = False # Show verbose output + all = False # Show all entries, including missing/invalid checkout folders + fetch = False # Auto-fetch before showing status + pull = False # Auto-pull before showing status + inspect_remotes = False # Inspect remote branches to report cleanable (slower) def __init__(self, **kwargs): self.update(**kwargs) @@ -300,13 +300,13 @@ def __init__(self, path, prefs=None): :param str path: Path to folder :param MgitPreferences|None prefs: Display prefs """ - self.path = path # Path to folder to examine - self.prefs = prefs or MgitPreferences() # Preferences on how to output result - self.checkouts = [] # Actual git checkouts in 'path' - self.projects = collections.defaultdict(set) # Seen remotes - self.predominant = None # Predominant remote, if any - self.additional = None # Additional projects (sorted by checkouts, descending) - self.stash_projects = {} # Corresponding projects from stash, when applicable + self.path = path # Path to folder to examine + self.prefs = prefs or MgitPreferences() # Preferences on how to output result + self.checkouts = [] # Actual git checkouts in 'path' + self.projects = collections.defaultdict(set) # Seen remotes + self.predominant = None # Predominant remote, if any + self.additional = None # Additional projects (sorted by checkouts, descending) + self.stash_projects = {} # Corresponding projects from stash, when applicable self.scan() def __repr__(self): diff --git a/src/mgit/git.py b/src/mgit/git.py index 96ef7cf..8537af0 100644 --- a/src/mgit/git.py +++ b/src/mgit/git.py @@ -28,7 +28,7 @@ def is_valid_branch_name(name): :param str|None name: Branch name to validate :return bool: True if branch name appears valid, as per https://wincent.com/wiki/Legal_Git_branch_names """ - if not name or name[0] == "." or ".." in name or name.endswith("/") or name.endswith(".lock"): + if not name or name[0] == "." or ".." in name or name.endswith(("/", ".lock")): return False for char in name: @@ -538,7 +538,7 @@ def local_cleanable_branches(self): """ :return set: Local branches that can be cleaned """ - result = set(name for name in self.orphan_branches if name not in self.special_branches) + result = {name for name in self.orphan_branches if name not in self.special_branches} for branch in self.remote_cleanable_branches: remote, _, name = branch.partition("/") tracking = self.config.tracking_remote.get(name) @@ -623,10 +623,10 @@ class GitBranches(GitAspect): _command = "branch --list --all" _remote_prefix = "remotes/" - current = "" # Current local branch - local = set() # Local branches - by_remote = collections.defaultdict(set) # Branches by remote (usually origin and optionally upstream) - default_branches = {} # Default branch per remote + current = "" # Current local branch + local = set() # Local branches + by_remote = collections.defaultdict(set) # Branches by remote (usually origin and optionally upstream) + default_branches = {} # Default branch per remote report = GitRunReport() @property @@ -640,13 +640,13 @@ def _process_line(self, line): name = line[2:] if name.startswith(self._remote_prefix): - name = name[len(self._remote_prefix):] + name = name[len(self._remote_prefix) :] default = None try: i = name.index(" -> ") first = name[:i] if first.endswith("/HEAD"): - default = name = name[i + 4:] + default = name = name[i + 4 :] except ValueError: pass @@ -675,9 +675,9 @@ class GitConfig(GitAspect): """Remote info""" _command = "config --list" - origin = GitURL() # URL to remote called 'origin' - remotes = {} # GitURL by remote name map - tracking_remote = {} # Remotes that each local branch is tracking + origin = GitURL() # URL to remote called 'origin' + remotes = {} # GitURL by remote name map + tracking_remote = {} # Remotes that each local branch is tracking content = {} @runez.cached_property @@ -790,12 +790,12 @@ def _report_sorter(enum): """ index, message = enum if message[0] == "<": - return -enum[0] # '<' makes message sort towards front, but keeping order with other such prefixed messages + return -enum[0] # '<' makes message sort towards front, but keeping order with other such prefixed messages if message[0] == ">": - return 1000000 + enum[0] # '>' makes message sort towards end + return 1000000 + enum[0] # '>' makes message sort towards end - return enum[0] # Non-prefixed message stay where they were + return enum[0] # Non-prefixed message stay where they were def _add_sorted(result, target, color, n, max_chars): diff --git a/tests/test_git.py b/tests/test_git.py index 233332b..965fe80 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -35,13 +35,7 @@ def test_git_urls(): check_url("/some/repo/foo", "file", "local", None, "/some/repo/foo", "repo", "foo") check_url( - "ssh://git@stash.corp.foo.com:7999/myproject/bin.git", - "ssh", - "stash.corp.foo.com", - "git", - "/myproject/bin.git", - "myproject", - "bin" + "ssh://git@stash.corp.foo.com:7999/myproject/bin.git", "ssh", "stash.corp.foo.com", "git", "/myproject/bin.git", "myproject", "bin" ) check_url( "https://user@stash.corp.foo.com/scm/myproject/bin.git", @@ -50,7 +44,7 @@ def test_git_urls(): "user", "/scm/myproject/bin.git", "myproject", - "bin" + "bin", ) check_url("git@github.com:foo/vmaf.git", "ssh", "github.com", "git", "foo/vmaf.git", "foo", "vmaf") diff --git a/tests/test_reporting.py b/tests/test_reporting.py index 694d8d0..55c5f94 100644 --- a/tests/test_reporting.py +++ b/tests/test_reporting.py @@ -15,12 +15,12 @@ def test_reporting(): assert GitRunReport.not_git().has_problems # Sorting - check_sorting("a b c", "a; b; c") # Messages stay in order they were provided - check_sorting("a b c b a", "a; b; c") # No dupes - check_sorting("a c d", "b; a; d; c") # > pushed message to back - check_sorting("a c e f", "d; b; a; f; c; e") # > ordered pushing + check_sorting("a b c", "a; b; c") # Messages stay in order they were provided + check_sorting("a b c b a", "a; b; c") # No dupes + check_sorting("a c d", "b; a; d; c") # > pushed message to back + check_sorting("a c e f", "d; b; a; f; c; e") # > ordered pushing # Typical issues check_sorting(GitRunReport.not_git(), "not a git checkout") @@ -51,14 +51,7 @@ def test_reporting(): check_sorting(GitRunReport().add(problem="p1", note="n1").add(problem="p2"), "p1; p2; n1") # Progress comes ahead of notes, but after problems - check_sorting( - GitRunReport().add( - note="n1 Date: Thu, 4 Dec 2025 22:51:45 -0800 Subject: [PATCH 3/5] Removed setup.py --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 4 +--- README.rst | 3 ++- docs/contributing.rst | 10 ++++---- pyproject.toml | 44 ++++++++++++++++++++++++++++++++++- requirements.txt | 3 --- setup.py | 34 --------------------------- tox.ini | 18 ++------------ 8 files changed, 55 insertions(+), 63 deletions(-) delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51aeb94..1f8b51b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - uses: astral-sh/setup-uv@v7 - run: uv venv - run: uv pip install -U tox-uv - - run: .venv/bin/tox -e py,docs,style + - run: .venv/bin/tox -e py,style - run: uv build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 98da5d7..1004478 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,8 +24,6 @@ jobs: - run: pip install -U pip tox - run: tox -e py - uses: codecov/codecov-action@v5 - with: - file: .tox/test-reports/coverage.xml linters: @@ -38,4 +36,4 @@ jobs: python-version: "3.14" - run: pip install -U pip tox - - run: tox -e docs,style + - run: tox -e style diff --git a/README.rst b/README.rst index 2046420..1151694 100644 --- a/README.rst +++ b/README.rst @@ -127,7 +127,8 @@ You can also compile from source:: git clone https://github.com/zsimic/mgit.git cd mgit - tox -e venv + uv venv + uv pip install -e . .venv/bin/mgit --help diff --git a/docs/contributing.rst b/docs/contributing.rst index 4bb16dc..5864091 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -10,7 +10,10 @@ To get going locally, simply do this:: git clone https://github.com/zsimic/mgit.git cd mgit - tox -e venv + uv venv + uv pip install -r requirements.txt + uv pip install -r tests/requirements.txt + uv pip install -e . # You have a venv now in ./.venv, use it, open it with pycharm etc source .venv/bin/activate @@ -24,12 +27,11 @@ To get going locally, simply do this:: Running the tests ================= -To run the tests, simply run ``tox``, this will run tests against all python versions you have locally installed. -You can use pyenv_ for example to get python installations. +To run the tests, simply run ``tox``. Run: -* ``tox -e py38`` (for example) to limit test run to only one python version. +* ``tox -e py314`` (for example) to limit test run to only one python version. * ``tox -e style`` to run style checks only diff --git a/pyproject.toml b/pyproject.toml index c410f56..de83533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,49 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools", "setuptools-scm"] build-backend = "setuptools.build_meta" +[tool.setuptools_scm] +local_scheme = "dirty-tag" + +[project] +name = "mgit" +authors = [ + {name = "Zoran Simic", email = "zoran@simicweb.com"}, +] +description = "Fetch collections of git projects" +readme = "README.rst" +requires-python = ">=3.10" +license = "MIT" +license-files = ["LICENSE.txt"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Utilities", +] +dependencies = [ + "click~=8.3", + "runez~=5.3", +] +dynamic = ["version"] + +[project.scripts] +mgit = "mgit.cli:main" + +[project.urls] +Source = "https://github.com/zsimic/mgit" + + [tool.ruff] cache-dir = ".tox/.ruff_cache" line-length = 140 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 3e0ad15..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# pinned -click==8.0.1 -runez==2.7.5 diff --git a/setup.py b/setup.py deleted file mode 100644 index 70964f9..0000000 --- a/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -from setuptools import setup - -if __name__ == "__main__": - setup( - name="mgit", - setup_requires="setupmeta", - versioning="dev", - author="Zoran Simic zoran@simicweb.com", - url="https://github.com/zsimic/mgit", - entry_points={ - "console_scripts": [ - "mgit = mgit.cli:main", - ], - }, - python_requires=">=3.10", - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Developers", - "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX", - "Operating System :: Unix", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Software Development :: Build Tools", - "Topic :: Utilities", - ], - ) diff --git a/tox.ini b/tox.ini index 45671f8..32c2a69 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ [tox] -envlist = py{310,314}, coverage, docs, style +envlist = py{310,314}, coverage, style [testenv] setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} usedevelop = True -deps = -rrequirements.txt - -rtests/requirements.txt +deps = -rtests/requirements.txt commands = pytest {posargs:-vv --cov=src/ --cov=tests/ --cov-report= tests/} [testenv:coverage] @@ -17,13 +16,6 @@ commands = coverage combine coverage xml coverage html -[testenv:docs] -skip_install = True -deps = check-manifest - readme-renderer -commands = check-manifest - python setup.py check --strict --restructuredtext - [testenv:style] skip_install = True deps = ruff @@ -37,12 +29,6 @@ commands = ruff check --fix ruff format -[check-manifest] -ignore = tests/** - docs/* - requirements.txt - tox.ini - [coverage:xml] output = .tox/test-reports/coverage.xml [coverage:html] From 5660d4173f427ce9ff913a3597514cd0505bbe4f Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 4 Dec 2025 22:59:27 -0800 Subject: [PATCH 4/5] Ran pyupgrade --- src/mgit/__init__.py | 16 ++++++++-------- src/mgit/cli.py | 6 +++--- src/mgit/git.py | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/mgit/__init__.py b/src/mgit/__init__.py index 91ae18d..788608f 100644 --- a/src/mgit/__init__.py +++ b/src/mgit/__init__.py @@ -54,12 +54,12 @@ def print_modified(items, color1, color2=None): for item in items: state = item[0:2] if color2: - state = "%s%s" % (color1(item[0]), color2(item[1])) + state = f"{color1(item[0])}{color2(item[1])}" elif color1: state = color1(state) - print(" %s %s" % (state, item[3:])) + print(f" {state} {item[3:]}") class MgitPreferences: @@ -91,7 +91,7 @@ def _value_representation(self, name): if value is False: return "!%s" % name - return "%s=%s" % (name, value) + return f"{name}={value}" def set_short(self, value): """ @@ -132,7 +132,7 @@ def __init__(self, url): self.name = url.repo or "unknown" def __repr__(self): - return "%s/%s" % (self.type, self.name) + return f"{self.type}/{self.name}" def __hash__(self): return hash(str(self)) @@ -212,7 +212,7 @@ def name(self): if not self.git.config.repo_name or not self.git.is_git_checkout or self.basename == self.git.config.repo_name: return self.basename - return "%s (%s)" % (self.basename, self.git.config.repo_name) + return f"{self.basename} ({self.git.config.repo_name})" @runez.cached_property def origin_project(self): @@ -374,16 +374,16 @@ def header(self): result = "%s:" % runez.purple(runez.short(self.path)) if not self.projects: - return "%s %s" % (result, runez.orange("no git folders")) + return "{} {}".format(result, runez.orange("no git folders")) if self.predominant: - result += runez.bold(" %s %s" % (len(self.projects[self.predominant]), self.predominant)) + result += runez.bold(f" {len(self.projects[self.predominant])} {self.predominant}") else: result += runez.orange(" no predominant project") if self.additional: - result += " (%s)" % runez.purple(", ".join("+%s %s" % (len(self.projects[project]), project) for project in self.additional)) + result += " (%s)" % runez.purple(", ".join(f"+{len(self.projects[project])} {project}" for project in self.additional)) return result diff --git a/src/mgit/cli.py b/src/mgit/cli.py index 153e065..06d89ad 100644 --- a/src/mgit/cli.py +++ b/src/mgit/cli.py @@ -122,7 +122,7 @@ def clean_show(target): else: for branch in target.git.local_cleanable_branches: - print(" %s branch %s can be cleaned" % (runez.bold("local"), runez.bold(branch))) + print(" {} branch {} can be cleaned".format(runez.bold("local"), runez.bold(branch))) if not target.git.remote_cleanable_branches: print(" No remote branches can be cleaned") @@ -174,7 +174,7 @@ def handle_single_clean(target, what): print("%s cleaned" % runez.plural(cleaned, "remote branch")) else: - print("%s/%s remote branches cleaned" % (cleaned, total)) + print(f"{cleaned}/{total} remote branches cleaned") target.git.reset_cached_properties() if what == "all": @@ -205,7 +205,7 @@ def handle_single_clean(target, what): print(runez.bold("%s cleaned" % runez.plural(cleaned, "local branch"))) else: - print(runez.orange("%s/%s local branches cleaned" % (cleaned, total))) + print(runez.orange(f"{cleaned}/{total} local branches cleaned")) target.git.reset_cached_properties() diff --git a/src/mgit/git.py b/src/mgit/git.py index 8537af0..18197ab 100644 --- a/src/mgit/git.py +++ b/src/mgit/git.py @@ -78,7 +78,7 @@ def __init__(self, *args, **kwargs): self.add(*args, **kwargs) def __repr__(self): - return "%s problems, %s progress, %s notes" % (len(self._problem), len(self._progress), len(self._note)) + return f"{len(self._problem)} problems, {len(self._progress)} progress, {len(self._note)} notes" def __contains__(self, text): """ @@ -239,7 +239,7 @@ def set(self, url): if m: self.protocol = "ssh" self.hostname = m.group(1) or "unknown" - self.relative_path = "%s/%s" % (m.group(2), m.group(3)) + self.relative_path = f"{m.group(2)}/{m.group(3)}" self.username = "git" self._set_name(m.group(3)) self._set_repo(m.group(2)) @@ -332,7 +332,7 @@ def _git_command(self, args): args_represented = "git %s" % " ".join(args) else: - args_represented = "git -C %s %s" % (runez.short(self.path), " ".join(args)) + args_represented = "git -C {} {}".format(runez.short(self.path), " ".join(args)) cmd.extend(["-C", self.path]) cmd.extend(args) @@ -566,7 +566,7 @@ def remote_cleanable_branches(self): if not url or url.protocol != "ssh": continue - result.update(["%s/%s" % (remote, branch) for branch in branches if branch not in self.special_branches]) + result.update([f"{remote}/{branch}" for branch in branches if branch not in self.special_branches]) return result @@ -664,7 +664,7 @@ def _process_line(self, line): name = name[:-1] name, _, problem = name.partition(" ") - self.report.add(note="%s %s" % (name, problem)) + self.report.add(note=f"{name} {problem}") self.local.add(name) if line[0] == "*": From fe2e6c04557dcaa3ea55b4d205da527bda41eca7 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 4 Dec 2025 23:10:45 -0800 Subject: [PATCH 5/5] Corrected doc and extraneous spaces --- docs/contributing.rst | 4 +--- tox.ini | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 5864091..a4a5d4f 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -11,9 +11,7 @@ To get going locally, simply do this:: cd mgit uv venv - uv pip install -r requirements.txt - uv pip install -r tests/requirements.txt - uv pip install -e . + uv pip install -r tests/requirements.txt -e . # You have a venv now in ./.venv, use it, open it with pycharm etc source .venv/bin/activate diff --git a/tox.ini b/tox.ini index 32c2a69..227fe47 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py{310,314}, coverage, style setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} usedevelop = True deps = -rtests/requirements.txt -commands = pytest {posargs:-vv --cov=src/ --cov=tests/ --cov-report= tests/} +commands = pytest {posargs:-vv --cov=src --cov=tests --cov-report= tests} [testenv:coverage] setenv = COVERAGE_FILE={toxworkdir}/.coverage