Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
with:
python-version: "3.14"

- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@v8.1.0
- run: uvx --with tox-uv tox -e py,style
- run: uv build
- name: Publish to PyPI
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@v8.1.0
- run: uvx --with tox-uv tox -e py

linters:
Expand All @@ -34,5 +34,5 @@ jobs:
with:
python-version: "3.14"

- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@v8.1.0
- run: uvx --with tox-uv tox -e style
202 changes: 125 additions & 77 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Fetch collections of git projects
=================================
mgit
====

A small companion for humans who like tidy git checkouts.

.. image:: https://img.shields.io/pypi/v/mgit.svg
:target: https://pypi.org/project/mgit/
Expand All @@ -14,122 +16,168 @@ Fetch collections of git projects
:alt: Python versions tested


Overview
========
What it is
==========

``mgit`` is a tiny CLI for people who like their git checkouts clean, current,
and easy to scan. It gives you a quick read on one repo, or on every repo
directly inside a workspace folder.

With ``mgit``, you can pull/fetch several projects at once,
and also auto-cleanup dangling branches (from past pull requests).
- What state did I leave this checkout in?
- Can I pull, or did I leave work pending here?
- Is a merged branch still hanging around, waiting to be cleaned?
- I just wrapped up a branch; can I safely get back to ``main`` and clear it away?

A colored output is provided if possible, ``mgit`` should come in handy in general for:
``mgit`` is not a git replacement. It is the compact, careful layer for those
ordinary moments where you would rather see the answer than reconstruct it
from a handful of commands.

- quickly getting an overview of what's up with N git projects
- fetch/pull N git objects at once
- clone missing projects (useful if you tend to clone projects from same remote in one common folder)

The rhythm
==========

Example usage
=============
Start with a glance::

``mgit`` can show you what's the status of all your git projects in a folder, for example my repos::
mgit # show status here, or across a workspace

~/dev/github: mgit
~/dev/github: 4 github/zsimic
mgit: [main] up to date
pickley: [main] 1 diff, up to date* last fetch 1w 4d ago
runez: [main] up to date* last fetch 1w 4d ago
setupmeta: [main] up to date* last fetch 3d 23h ago
Refresh or move forward only when you mean to::

mgit f # fetch --all --prune, then show the refreshed status
mgit p # pull --rebase, but never over pending work

Here we can see that:
And for the wonderfully common ``PR merged`` moment::

- There are 4 git repos in folder ``~/dev/github``
mgit g # fetch, return to the default branch, pull, clean this branch

- All 4 come from ``github/zsimic``
The short names are the intended interface:

- 3 of them haven't been fetched in a while
- ``status`` / ``s``: show whether anything here needs attention. This is the default.
- ``fetch`` / ``f``: refresh remote refs, then show what changed.
- ``pull`` / ``p``: pull with rebase only when pending work will not be disturbed.
- ``main`` / ``m``: checkout the default branch, even if it is ``master``.
- ``branches`` / ``b``: list local branches with useful small annotations.
- ``groom`` / ``g``: safely finish with the merged branch you are currently on.

We can fetch them all at once with ``--fetch`` (or ``-f``)::
The command shape is::

~/dev/github: mgit --fetch
~/dev/github: 4 github/zsimic
mgit: [main] up to date
pickley: [main] 1 diff, up to date
runez: [main] behind 2
setupmeta: [main] up to date
mgit [-v] [--color auto|always|never] [COMMAND] [FOLDER]

You can pass a folder to most commands::

Now all projects have been refreshed, and we can see there's nothing new in 2 of them,
but one is 2 commits behind (ie: 2 commits are on the remote, not pulled yet).
The output also shows that one of the projects has uncommitted files.
mgit ~/github
mgit f ~/github
mgit g ~/github/mgit

Modified files are shown (by default) if only one project is in scope, for example::
Workspace scans are shallow on purpose: ``mgit ~/github`` inspects direct
children like ``~/github/*/.git`` and does not crawl nested folders.

~/dev/github: mgit pickley
pickley: [main] 1 diff, up to date
M tox.ini

What you see
============

A workspace stays pleasantly skimmable::

Above, we can see that the modified file in question is ``tox.ini`` in that project.
We can get the same effect using the ``--verbose`` (or ``-v``) flag,
like for example with 2 projects with modified files::
mgit: main ✅
pickley: main ☑️ [+1🪦] ⌛4w 6d
runez: feature 🪦 ✏️1
detached: HEAD 👻

~/dev/github: mgit -v
~/dev/github: 4 github/zsimic
mgit: [main] 1 diff, up to date
M README.rst
pickley: [main] 1 diff, up to date
M tox.ini
runez: [main] up to date
setupmeta: [main] up to date
In one glance you get:

- the current branch
- local diffs and untracked files
- ahead/behind/gone tracking state
- fetch freshness
- a compact hint that a stale local branch is still lurking

Synopsis::
``✅`` means recently refreshed, ``☑️`` means the local picture may be older,
``⌛`` calls out notably stale fetch information, a branch-level ``🪦`` means
its upstream is gone, and ``👻`` means detached ``HEAD``. A bracket such as
``[+2🪦+1]`` says that, besides the current and default branches, two local
branches have been proven cleanable and one remains.

~/dev/github: mgit --help
Usage: mgit [OPTIONS] [TARGET]
For a single checkout, status also prints the pending paths. Workspace output
keeps to one line per repo, so you can keep checking a directory full of
projects without turning the terminal into a log dump::

Fetch collections of git projects
mgit ~/github/mgit

Options:
--version Show the version and exit.
--debug Show debugging information.
--color / --no-color Use colors (on by default on ttys)
--log PATH Override log file location.
--clean [show|local|remote|all|reset]
Auto-clean branches
-f, --fetch Fetch from all remotes
-p, --pull Pull from tracking remote
-s, --short / -v, --verbose Short/verbose output
-cs Handy shortcut for '--clean show'
-cl Handy shortcut for '--clean local'
-cr Handy shortcut for '--clean remote'
-ca Handy shortcut for '--clean all'
-h, --help Show this message and exit.
Color is automatic on terminals and can be controlled explicitly::

Installation
mgit --color auto
mgit --color always
mgit --color never


Safety model
============

Easiest way to get mgit is via pickley_ or pipx_::
``mgit`` is read-only by default. Asking how things look does not change your repos.

pickley install mgit
Commands that act are explicit:

- ``mgit f`` updates local remote refs with ``git fetch --all --prune``.
- ``mgit p`` pulls with rebase only when the checkout passes safety checks.
- ``mgit g`` fetches, verifies that the current branch can be cleaned before
switching branches, pulls safely, and deletes that local branch only. If it
still exists on ``origin``, it deletes that one remote branch only after
independently proving that the fetched remote ref is merged or
content-equivalent to the fetched default branch; an exact-ref lease prevents
deleting a branch that advanced meanwhile. No other branch is deleted.
When already on the default branch, it fetches and reports that fact without
pulling or cleaning branches. Use ``mgit m`` when you only want to switch to
the default branch.


Coming next
===========

A larger convenience waiting in the wings is ``mgit clone``: give it a full
repo URL and let ``mgit`` choose the local destination from simple config
rules.

Planned config shape::

locations = [
{ match = "github.com/zsimic/*", dir = "~/github" },
{ match = "github.com/*", dir = "~/ext" },
{ match = "git.mycompany.com/*", dir = "~/dev" },
]

The goal is predictable placement without memorizing where each family of repos
belongs. Status, fetch, pull, main, and groom do not require any mgit config.


Install
=======

Install with pickley_ or pipx_::

pickley install mgit

or::

pipx install mgit


You can also compile from source::
Install from a checkout for development::

git clone https://github.com/zsimic/mgit.git
cd mgit
uv venv
uv pip install -e .

uv sync
.venv/bin/mgit --help

source .venv/bin/activate
mgit --help

Develop
=======

Fast local checks::

.venv/bin/pytest -q
ruff check

Full confidence check::

tox


.. _pickley: https://pypi.org/project/pickley/
Expand Down
29 changes: 18 additions & 11 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Contributions are welcome!

tox_ is used for building and testing, ``setup.py`` is kept simple thanks to setupmeta_.
tox_ is used for full test runs. Packaging metadata lives in
``pyproject.toml`` and versions come from ``setuptools-scm``.

Development
===========
Expand All @@ -10,10 +11,9 @@ To get going locally, simply do this::
git clone https://github.com/zsimic/mgit.git
cd mgit

uv venv
uv pip install -r tests/requirements.txt -e .
uv sync

# You have a venv now in ./.venv, use it, open it with pycharm etc
# You have a venv now in ./.venv
source .venv/bin/activate
which python
which mgit
Expand All @@ -25,15 +25,24 @@ To get going locally, simply do this::
Running the tests
=================

To run the tests, simply run ``tox``.
Fast local checks::

Run:
.venv/bin/pytest -q
ruff check

* ``tox -e py314`` (for example) to limit test run to only one python version.
Full confidence check::

* ``tox -e style`` to run style checks only
tox

* etc
Useful focused tox runs:

* ``tox -e py39`` for the minimum supported Python version.

* ``tox -e py314`` for the newest supported Python version.

* ``tox -e style`` for packaged lint/type checks.

* ``tox -e docs`` for a strict ``README.rst`` parse check.


Test coverage
Expand All @@ -45,5 +54,3 @@ Run ``tox``, then open ``.tox/test-reports/htmlcov/index.html``
.. _pyenv: https://github.com/pyenv/pyenv

.. _tox: https://github.com/tox-dev/tox

.. _setupmeta: https://pypi.org/project/setupmeta/
14 changes: 11 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ authors = [
]
description = "Fetch collections of git projects"
readme = "README.rst"
requires-python = ">=3.10"
requires-python = ">=3.9"
license = "MIT"
license-files = ["LICENSE.txt"]
classifiers = [
Expand All @@ -24,6 +24,7 @@ classifiers = [
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -32,7 +33,6 @@ classifiers = [
"Topic :: Utilities",
]
dependencies = [
"click<9",
"runez<6",
]
dynamic = ["version"]
Expand All @@ -43,8 +43,16 @@ mgit = "mgit.cli:main"
[project.urls]
Source = "https://github.com/zsimic/mgit"

[dependency-groups]
test = [
"pytest-cov",
]
dev = [
{ include-group = "test" },
]

[tool.pyright]
pythonVersion = "3.10"
pythonVersion = "3.9"
include = ["src"]

[tool.ruff]
Expand Down
Loading
Loading