mud-git 1.1.6__tar.gz → 1.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mud_git-1.1.6 → mud_git-1.1.7}/.github/workflows/publish-aur.yaml +26 -1
- mud_git-1.1.7/.github/workflows/publish-pypi.yaml +59 -0
- mud_git-1.1.7/.github/workflows/test.yaml +38 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/.gitignore +3 -0
- mud_git-1.1.7/AGENTS.md +70 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/PKG-INFO +4 -2
- {mud_git-1.1.6 → mud_git-1.1.7}/README.md +1 -1
- {mud_git-1.1.6 → mud_git-1.1.7}/pyproject.toml +8 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/app.py +5 -2
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/config.py +6 -2
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/runner.py +3 -4
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/styles.py +0 -1
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud_git.egg-info/PKG-INFO +4 -2
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud_git.egg-info/SOURCES.txt +11 -1
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud_git.egg-info/requires.txt +3 -0
- mud_git-1.1.7/src/mud_git.egg-info/scm_file_list.json +32 -0
- mud_git-1.1.7/src/mud_git.egg-info/scm_version.json +8 -0
- mud_git-1.1.7/tests/conftest.py +29 -0
- mud_git-1.1.7/tests/helpers.py +102 -0
- mud_git-1.1.7/tests/test_config.py +93 -0
- mud_git-1.1.7/tests/test_display.py +71 -0
- mud_git-1.1.7/tests/test_filters.py +57 -0
- mud_git-1.1.7/tests/test_run.py +55 -0
- mud_git-1.1.7/tests/test_states.py +86 -0
- mud_git-1.1.6/.github/workflows/publish-pypi.yaml +0 -31
- mud_git-1.1.6/.github/workflows/test.yaml +0 -110
- {mud_git-1.1.6 → mud_git-1.1.7}/.editorconfig +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/LICENSE +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/PKGBUILD +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/img.png +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/requirements.txt +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/setup.cfg +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/__init__.py +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/__main__.py +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/commands.py +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/settings.py +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud/utils.py +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud_git.egg-info/dependency_links.txt +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud_git.egg-info/entry_points.txt +0 -0
- {mud_git-1.1.6 → mud_git-1.1.7}/src/mud_git.egg-info/top_level.txt +0 -0
|
@@ -6,11 +6,36 @@ on:
|
|
|
6
6
|
workflow_dispatch:
|
|
7
7
|
|
|
8
8
|
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Set up Python 3.13
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.13"
|
|
18
|
+
|
|
19
|
+
- name: Configure git
|
|
20
|
+
run: |
|
|
21
|
+
git config --global user.name "Test User"
|
|
22
|
+
git config --global user.email "test@example.com"
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies and build
|
|
25
|
+
run: |
|
|
26
|
+
pip install -r requirements.txt pytest build
|
|
27
|
+
python -m build
|
|
28
|
+
pip install dist/*.whl
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: pytest -v
|
|
32
|
+
|
|
9
33
|
aur-publish:
|
|
34
|
+
needs: [test]
|
|
10
35
|
runs-on: ubuntu-latest
|
|
11
36
|
steps:
|
|
12
37
|
- name: Checkout repository
|
|
13
|
-
uses: actions/checkout@
|
|
38
|
+
uses: actions/checkout@v4
|
|
14
39
|
with:
|
|
15
40
|
fetch-depth: 0
|
|
16
41
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Upload Python Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python 3.13
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.13"
|
|
21
|
+
|
|
22
|
+
- name: Configure git
|
|
23
|
+
run: |
|
|
24
|
+
git config --global user.name "Test User"
|
|
25
|
+
git config --global user.email "test@example.com"
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies and build
|
|
28
|
+
run: |
|
|
29
|
+
pip install -r requirements.txt pytest build
|
|
30
|
+
python -m build
|
|
31
|
+
pip install dist/*.whl
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: pytest -v
|
|
35
|
+
|
|
36
|
+
deploy:
|
|
37
|
+
needs: [test]
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Set up Python
|
|
43
|
+
uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: '3.x'
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: |
|
|
49
|
+
python -m pip install --upgrade pip
|
|
50
|
+
pip install build
|
|
51
|
+
|
|
52
|
+
- name: Build package
|
|
53
|
+
run: python -m build
|
|
54
|
+
|
|
55
|
+
- name: Publish package
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
|
57
|
+
with:
|
|
58
|
+
user: __token__
|
|
59
|
+
password: ${{ secrets.TWINE_PAT }}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- '*'
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- master
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python 3.13
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
|
|
26
|
+
- name: Configure git
|
|
27
|
+
run: |
|
|
28
|
+
git config --global user.name "Test User"
|
|
29
|
+
git config --global user.email "test@example.com"
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies and build
|
|
32
|
+
run: |
|
|
33
|
+
pip install -r requirements.txt pytest build
|
|
34
|
+
python -m build
|
|
35
|
+
pip install dist/*.whl
|
|
36
|
+
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: pytest -v
|
mud_git-1.1.7/AGENTS.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# AI Agent Instructions
|
|
2
|
+
|
|
3
|
+
## What `mud` does
|
|
4
|
+
|
|
5
|
+
`mud` runs git commands (and arbitrary shell commands) across multiple repositories simultaneously. It reads a `.mudconfig` TSV file listing repo paths and optional labels, then dispatches to every matching repo with filtering, async execution, and rich terminal output.
|
|
6
|
+
|
|
7
|
+
For commands, flags, and settings refer to `README.md` — it is the source of truth for user-facing behaviour. For runtime dependencies refer to `requirements.txt`.
|
|
8
|
+
|
|
9
|
+
## Build and test
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pip install -e ".[dev]" # install with dev deps
|
|
13
|
+
python -m build # build wheel
|
|
14
|
+
pytest # run tests (verbose by default)
|
|
15
|
+
pytest tests/test_run.py # run a single file
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Entry point
|
|
19
|
+
|
|
20
|
+
`mud` CLI → `mud:run` in `src/mud/__init__.py` → `App` in `src/mud/app.py`
|
|
21
|
+
|
|
22
|
+
## Module map
|
|
23
|
+
|
|
24
|
+
| File | Purpose |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `src/mud/__init__.py` | Entry point; initialises `Settings`, creates `App`, calls `run()` |
|
|
27
|
+
| `src/mud/app.py` | CLI dispatch; parses args, applies filters, calls `Runner` |
|
|
28
|
+
| `src/mud/runner.py` | All display commands and execution modes |
|
|
29
|
+
| `src/mud/config.py` | `.mudconfig` TSV read/write; `init`, `add`, `remove`, `prune` |
|
|
30
|
+
| `src/mud/settings.py` | `~/.config/mud/settings.ini` read/write |
|
|
31
|
+
| `src/mud/commands.py` | Constants for every command name and filter flag prefix |
|
|
32
|
+
| `src/mud/styles.py` | ANSI escape codes and Nerd Font glyphs |
|
|
33
|
+
| `src/mud/utils.py` | Shared helpers: table creation, error printing, configure wizard |
|
|
34
|
+
|
|
35
|
+
## Architecture notes
|
|
36
|
+
|
|
37
|
+
**Global settings instance** — `utils.settings` is a single `Settings` object created in `__init__.run()` and accessed across all modules.
|
|
38
|
+
|
|
39
|
+
**Command dispatch** — `App.run()` routes to either a native `Runner` method (matched against constants in `commands.py`) or a shell pass-through. The `--` separator and `-c=<cmd>` flag both reach the shell path.
|
|
40
|
+
|
|
41
|
+
**Execution modes** — three modes controlled by `run_async` + `run_table` settings (toggled by `-a` / `-t` flags): sequential, async streamed, async live-table.
|
|
42
|
+
|
|
43
|
+
**Filter chain** — `App._filter_with_arguments()` applies up to eight filters in sequence (ignore label, include/exclude label, include/exclude branch, name substring, modified, diverged). Each step removes non-matching repos.
|
|
44
|
+
|
|
45
|
+
**Nerd Fonts** — every glyph in `styles.GLYPHS` has an ASCII fallback. `utils.glyphs(key)` selects between them based on the `nerd_fonts` setting, so mud works with or without a patched font.
|
|
46
|
+
|
|
47
|
+
## Tests
|
|
48
|
+
|
|
49
|
+
Tests are black-box CLI tests — each runs `python -m mud` as a subprocess against real git repos in a temporary directory.
|
|
50
|
+
|
|
51
|
+
| File | Covers |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `tests/test_config.py` | `init`, `add`, `remove`, `prune` |
|
|
54
|
+
| `tests/test_display.py` | `status`, `info`, `log`, `labels`, `branches`, `tags` |
|
|
55
|
+
| `tests/test_run.py` | Execution modes and flags |
|
|
56
|
+
| `tests/test_filters.py` | `-l=`, `-L=`, `-b=`, `-B=`, `-n=` filter flags |
|
|
57
|
+
| `tests/test_states.py` | Edge-case repo states (unborn, detached, rebasing) |
|
|
58
|
+
|
|
59
|
+
## Knowledge base update rule
|
|
60
|
+
After editing any file under `src/mud/`, update the affected sections of this file before finishing the task.
|
|
61
|
+
`README.md` is the source of truth for user-facing behaviour — if commands, flags, or settings changed, update the relevant table rows there. Table edits only — no new prose.
|
|
62
|
+
|
|
63
|
+
# Git
|
|
64
|
+
|
|
65
|
+
## Branching
|
|
66
|
+
- All new features should have `feature/` prefix
|
|
67
|
+
- Bugfixes are using `bugfix/` prefix
|
|
68
|
+
|
|
69
|
+
## Tags
|
|
70
|
+
Tags format is vX.Y.Z
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mud-git
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.7
|
|
4
4
|
Summary: Multi repository git utility. Manage multiple git-repositories simultaneously.
|
|
5
5
|
Author-email: Jasur Sadikov <jasur@sadikoff.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,6 +11,8 @@ Description-Content-Type: text/markdown
|
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Dist: prettytable
|
|
13
13
|
Requires-Dist: pygit2
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
16
|
Dynamic: license-file
|
|
15
17
|
|
|
16
18
|
# mud
|
|
@@ -32,7 +34,7 @@ pip install mud-git
|
|
|
32
34
|
```
|
|
33
35
|
**ArchLinux**
|
|
34
36
|
```bash
|
|
35
|
-
|
|
37
|
+
yay -S mud-git
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
For requirements check [requirements.txt](requirements.txt).
|
|
@@ -22,6 +22,14 @@ local_scheme = "no-local-version"
|
|
|
22
22
|
[tool.hatch.build.targets.wheel]
|
|
23
23
|
packages = ["src/mud"]
|
|
24
24
|
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest"]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = ["tests"]
|
|
30
|
+
pythonpath = ["tests"]
|
|
31
|
+
addopts = "-v"
|
|
32
|
+
|
|
25
33
|
[project.scripts]
|
|
26
34
|
mud = "mud:run"
|
|
27
35
|
|
|
@@ -35,7 +35,10 @@ class App:
|
|
|
35
35
|
|
|
36
36
|
@staticmethod
|
|
37
37
|
def _create_parser() -> ArgumentParser:
|
|
38
|
-
parser = ArgumentParser(
|
|
38
|
+
parser = ArgumentParser(
|
|
39
|
+
description=f'mud allows you to run commands in multiple repositories.',
|
|
40
|
+
usage='%(prog)s [OPTIONS] [COMMAND] ...',
|
|
41
|
+
)
|
|
39
42
|
subparsers = parser.add_subparsers(dest='command')
|
|
40
43
|
|
|
41
44
|
subparsers.add_parser(LOG[0], aliases=LOG[1:], help='Displays log of latest commit messages for all repositories in a table view.')
|
|
@@ -130,7 +133,7 @@ class App:
|
|
|
130
133
|
|
|
131
134
|
# Handling commands
|
|
132
135
|
if native_command:
|
|
133
|
-
args = self.parser.
|
|
136
|
+
args, _ = self.parser.parse_known_args()
|
|
134
137
|
|
|
135
138
|
if args.command in INIT + ADD + REMOVE + PRUNE + GET_CONFIG:
|
|
136
139
|
if args.command in GET_CONFIG:
|
|
@@ -41,8 +41,10 @@ class Config:
|
|
|
41
41
|
|
|
42
42
|
for path, labels in self.data.items():
|
|
43
43
|
valid_labels = [label for label in labels if _filter_labels(label)]
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if valid_labels:
|
|
45
|
+
writer.writerow([path, ','.join(valid_labels)])
|
|
46
|
+
else:
|
|
47
|
+
writer.writerow([path])
|
|
46
48
|
|
|
47
49
|
def load(self, file_path: str) -> None:
|
|
48
50
|
self.data = {}
|
|
@@ -108,6 +110,8 @@ class Config:
|
|
|
108
110
|
|
|
109
111
|
config_path = os.path.join(config_dir, utils.CONFIG_FILE_NAME)
|
|
110
112
|
path = os.path.relpath(current_path, config_path)
|
|
113
|
+
if path is not None:
|
|
114
|
+
path = path.rstrip('/')
|
|
111
115
|
if path is None:
|
|
112
116
|
path = label
|
|
113
117
|
label = None
|
|
@@ -413,10 +413,9 @@ class Runner:
|
|
|
413
413
|
|
|
414
414
|
@staticmethod
|
|
415
415
|
def _print_process_header(path: str, command: str, failed: bool, code: int) -> None:
|
|
416
|
-
|
|
417
|
-
code = f'{
|
|
418
|
-
|
|
419
|
-
print(f'{command}{code}{path}')
|
|
416
|
+
path = f'{BKG_BLACK}{BRIGHT_WHITE} {Runner._get_formatted_path(path)} {RESET}{BLACK}{BKG_RED if failed else BKG_GREEN}{glyphs(')')}{RESET}'
|
|
417
|
+
code = f'{BKG_RED if failed else BKG_GREEN} {BRIGHT_WHITE}{command}{f':{BOLD}{code}' if failed else ''} {RESET}{RED if failed else GREEN}{glyphs(')')}{RESET}'
|
|
418
|
+
print(f'{path}{code}')
|
|
420
419
|
|
|
421
420
|
@staticmethod
|
|
422
421
|
def _get_formatted_path(path: str, file_system: bool = True, color: str = '') -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mud-git
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.7
|
|
4
4
|
Summary: Multi repository git utility. Manage multiple git-repositories simultaneously.
|
|
5
5
|
Author-email: Jasur Sadikov <jasur@sadikoff.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,6 +11,8 @@ Description-Content-Type: text/markdown
|
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Dist: prettytable
|
|
13
13
|
Requires-Dist: pygit2
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
16
|
Dynamic: license-file
|
|
15
17
|
|
|
16
18
|
# mud
|
|
@@ -32,7 +34,7 @@ pip install mud-git
|
|
|
32
34
|
```
|
|
33
35
|
**ArchLinux**
|
|
34
36
|
```bash
|
|
35
|
-
|
|
37
|
+
yay -S mud-git
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
For requirements check [requirements.txt](requirements.txt).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
.editorconfig
|
|
2
2
|
.gitignore
|
|
3
|
+
AGENTS.md
|
|
3
4
|
LICENSE
|
|
4
5
|
PKGBUILD
|
|
5
6
|
README.md
|
|
@@ -23,4 +24,13 @@ src/mud_git.egg-info/SOURCES.txt
|
|
|
23
24
|
src/mud_git.egg-info/dependency_links.txt
|
|
24
25
|
src/mud_git.egg-info/entry_points.txt
|
|
25
26
|
src/mud_git.egg-info/requires.txt
|
|
26
|
-
src/mud_git.egg-info/
|
|
27
|
+
src/mud_git.egg-info/scm_file_list.json
|
|
28
|
+
src/mud_git.egg-info/scm_version.json
|
|
29
|
+
src/mud_git.egg-info/top_level.txt
|
|
30
|
+
tests/conftest.py
|
|
31
|
+
tests/helpers.py
|
|
32
|
+
tests/test_config.py
|
|
33
|
+
tests/test_display.py
|
|
34
|
+
tests/test_filters.py
|
|
35
|
+
tests/test_run.py
|
|
36
|
+
tests/test_states.py
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
".editorconfig",
|
|
4
|
+
".github/workflows/publish-aur.yaml",
|
|
5
|
+
".github/workflows/publish-pypi.yaml",
|
|
6
|
+
".github/workflows/test.yaml",
|
|
7
|
+
".gitignore",
|
|
8
|
+
"AGENTS.md",
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"PKGBUILD",
|
|
11
|
+
"README.md",
|
|
12
|
+
"img.png",
|
|
13
|
+
"pyproject.toml",
|
|
14
|
+
"requirements.txt",
|
|
15
|
+
"src/mud/__init__.py",
|
|
16
|
+
"src/mud/__main__.py",
|
|
17
|
+
"src/mud/app.py",
|
|
18
|
+
"src/mud/commands.py",
|
|
19
|
+
"src/mud/config.py",
|
|
20
|
+
"src/mud/runner.py",
|
|
21
|
+
"src/mud/settings.py",
|
|
22
|
+
"src/mud/styles.py",
|
|
23
|
+
"src/mud/utils.py",
|
|
24
|
+
"tests/conftest.py",
|
|
25
|
+
"tests/helpers.py",
|
|
26
|
+
"tests/test_config.py",
|
|
27
|
+
"tests/test_display.py",
|
|
28
|
+
"tests/test_filters.py",
|
|
29
|
+
"tests/test_run.py",
|
|
30
|
+
"tests/test_states.py"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from helpers import make_git_repo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def home(tmp_path: Path) -> Path:
|
|
8
|
+
"""An isolated home directory so the mud subprocess gets clean default settings."""
|
|
9
|
+
h = tmp_path / "home"
|
|
10
|
+
h.mkdir()
|
|
11
|
+
return h
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def repos(tmp_path: Path) -> Path:
|
|
16
|
+
"""Two git repos (repo_a, repo_b) with a .mudconfig in tmp_path."""
|
|
17
|
+
make_git_repo(tmp_path / "repo_a")
|
|
18
|
+
make_git_repo(tmp_path / "repo_b")
|
|
19
|
+
(tmp_path / ".mudconfig").write_text("repo_a\t\nrepo_b\t\n")
|
|
20
|
+
return tmp_path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def repos_labeled(tmp_path: Path) -> Path:
|
|
25
|
+
"""Same as repos but each repo has a distinct label."""
|
|
26
|
+
make_git_repo(tmp_path / "repo_a")
|
|
27
|
+
make_git_repo(tmp_path / "repo_b")
|
|
28
|
+
(tmp_path / ".mudconfig").write_text("repo_a\tlabel_a\nrepo_b\tlabel_b\n")
|
|
29
|
+
return tmp_path
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Internal helper — runs a shell command inside a directory
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
def _run(cmd: str, cwd: Path, check: bool = True) -> subprocess.CompletedProcess:
|
|
12
|
+
return subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, text=True, check=check)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Repo factory helpers
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
def make_git_repo(path: Path) -> None:
|
|
20
|
+
"""A normal repo: initialised, one commit, on the default branch."""
|
|
21
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
(path / "README.md").write_text(f"# {path.name}\n")
|
|
23
|
+
_run("git init", path)
|
|
24
|
+
_run("git config user.name 'Test User'", path)
|
|
25
|
+
_run("git config user.email 'test@example.com'", path)
|
|
26
|
+
_run("git add .", path)
|
|
27
|
+
_run("git commit -m 'Initial commit'", path)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def make_empty_repo(path: Path) -> None:
|
|
31
|
+
"""A repo that has been initialised but has no commits (HEAD is unborn)."""
|
|
32
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
_run("git init", path)
|
|
34
|
+
_run("git config user.name 'Test User'", path)
|
|
35
|
+
_run("git config user.email 'test@example.com'", path)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def make_detached_repo(path: Path) -> None:
|
|
39
|
+
"""A repo whose HEAD is detached at a plain commit (not a tag)."""
|
|
40
|
+
make_git_repo(path)
|
|
41
|
+
commit = _run("git rev-parse HEAD", path).stdout.strip()
|
|
42
|
+
_run(f"git checkout {commit}", path)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def make_tagged_repo(path: Path) -> None:
|
|
46
|
+
"""A repo checked out at an annotated tag, leaving HEAD detached."""
|
|
47
|
+
make_git_repo(path)
|
|
48
|
+
_run("git tag v1.0", path)
|
|
49
|
+
_run("git checkout v1.0", path)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def make_rebasing_repo(path: Path) -> None:
|
|
53
|
+
"""A repo that is mid-rebase because of a conflict.
|
|
54
|
+
|
|
55
|
+
Creates two branches with a conflicting edit to the same file,
|
|
56
|
+
then starts a rebase on the feature branch so the repo is left in
|
|
57
|
+
the 'rebasing' state (HEAD detached, .git/REBASE_HEAD present).
|
|
58
|
+
"""
|
|
59
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
(path / "file.txt").write_text("original\n")
|
|
61
|
+
_run("git init", path)
|
|
62
|
+
_run("git config user.name 'Test User'", path)
|
|
63
|
+
_run("git config user.email 'test@example.com'", path)
|
|
64
|
+
_run("git add .", path)
|
|
65
|
+
_run("git commit -m 'initial'", path)
|
|
66
|
+
|
|
67
|
+
main_branch = _run("git branch --show-current", path).stdout.strip()
|
|
68
|
+
|
|
69
|
+
_run("git checkout -b feature", path)
|
|
70
|
+
(path / "file.txt").write_text("feature change\n")
|
|
71
|
+
_run("git add .", path)
|
|
72
|
+
_run("git commit -m 'feature'", path)
|
|
73
|
+
|
|
74
|
+
_run(f"git checkout {main_branch}", path)
|
|
75
|
+
(path / "file.txt").write_text("main change\n")
|
|
76
|
+
_run("git add .", path)
|
|
77
|
+
_run("git commit -m 'main change'", path)
|
|
78
|
+
|
|
79
|
+
_run("git checkout feature", path)
|
|
80
|
+
# Intentionally not check=True — the rebase stops mid-way on the conflict.
|
|
81
|
+
_run(f"git rebase {main_branch}", path, check=False)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# CLI runner
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def run_mud(*args: str, cwd: Path, home: Path) -> subprocess.CompletedProcess:
|
|
89
|
+
"""Run ``python -m mud <args>`` inside *cwd* and return the result.
|
|
90
|
+
|
|
91
|
+
HOME is pointed at an empty temp directory so the subprocess gets
|
|
92
|
+
clean default mud settings, isolated from the developer's own machine.
|
|
93
|
+
"""
|
|
94
|
+
env = os.environ.copy()
|
|
95
|
+
env["HOME"] = str(home)
|
|
96
|
+
return subprocess.run(
|
|
97
|
+
[sys.executable, "-m", "mud", *args],
|
|
98
|
+
cwd=cwd,
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
env=env,
|
|
102
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for config-management commands: init, add, remove, prune.
|
|
3
|
+
|
|
4
|
+
Each test runs `mud <command>` as a real subprocess and checks the
|
|
5
|
+
exit code plus the contents of .mudconfig on disk.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from helpers import make_git_repo, run_mud
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# mud init
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
def test_init_creates_mudconfig(tmp_path: Path, home: Path):
|
|
16
|
+
"""mud init creates .mudconfig and adds all git repos it finds."""
|
|
17
|
+
make_git_repo(tmp_path / "repo_a")
|
|
18
|
+
make_git_repo(tmp_path / "repo_b")
|
|
19
|
+
|
|
20
|
+
result = run_mud("init", cwd=tmp_path, home=home)
|
|
21
|
+
|
|
22
|
+
assert result.returncode == 0
|
|
23
|
+
mudconfig = (tmp_path / ".mudconfig").read_text()
|
|
24
|
+
assert "repo_a" in mudconfig
|
|
25
|
+
assert "repo_b" in mudconfig
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_init_is_idempotent(tmp_path: Path, home: Path):
|
|
29
|
+
"""Running mud init twice does not duplicate repos in .mudconfig."""
|
|
30
|
+
make_git_repo(tmp_path / "repo_a")
|
|
31
|
+
|
|
32
|
+
run_mud("init", cwd=tmp_path, home=home)
|
|
33
|
+
run_mud("init", cwd=tmp_path, home=home)
|
|
34
|
+
|
|
35
|
+
mudconfig = (tmp_path / ".mudconfig").read_text()
|
|
36
|
+
assert mudconfig.count("repo_a") == 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# mud add
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def test_add_repo(repos: Path, home: Path):
|
|
44
|
+
"""mud add <path> registers a new repo in .mudconfig."""
|
|
45
|
+
make_git_repo(repos / "repo_c")
|
|
46
|
+
|
|
47
|
+
result = run_mud("add", "repo_c", cwd=repos, home=home)
|
|
48
|
+
|
|
49
|
+
assert result.returncode == 0
|
|
50
|
+
assert "repo_c" in (repos / ".mudconfig").read_text()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_add_repo_with_label(repos: Path, home: Path):
|
|
54
|
+
"""mud add <path> <label> registers a repo with a label."""
|
|
55
|
+
make_git_repo(repos / "repo_c")
|
|
56
|
+
|
|
57
|
+
run_mud("add", "repo_c", "my_label", cwd=repos, home=home)
|
|
58
|
+
|
|
59
|
+
# Verify label appears in the labels command output
|
|
60
|
+
result = run_mud("labels", cwd=repos, home=home)
|
|
61
|
+
assert "my_label" in result.stdout
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# mud remove
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
def test_remove_repo(repos: Path, home: Path):
|
|
69
|
+
"""mud remove <path> drops the repo from .mudconfig."""
|
|
70
|
+
result = run_mud("remove", "repo_a", cwd=repos, home=home)
|
|
71
|
+
|
|
72
|
+
assert result.returncode == 0
|
|
73
|
+
mudconfig = (repos / ".mudconfig").read_text()
|
|
74
|
+
assert "repo_a" not in mudconfig
|
|
75
|
+
assert "repo_b" in mudconfig # sibling repo must be untouched
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# mud prune
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
def test_prune_removes_missing_paths(tmp_path: Path, home: Path):
|
|
83
|
+
"""mud prune removes entries whose paths no longer exist on disk."""
|
|
84
|
+
make_git_repo(tmp_path / "repo_a")
|
|
85
|
+
# ghost_repo is listed in .mudconfig but does not exist on disk
|
|
86
|
+
(tmp_path / ".mudconfig").write_text("repo_a\t\nghost_repo\t\n")
|
|
87
|
+
|
|
88
|
+
result = run_mud("prune", cwd=tmp_path, home=home)
|
|
89
|
+
|
|
90
|
+
assert result.returncode == 0
|
|
91
|
+
mudconfig = (tmp_path / ".mudconfig").read_text()
|
|
92
|
+
assert "repo_a" in mudconfig
|
|
93
|
+
assert "ghost_repo" not in mudconfig
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for read-only display commands: status, info, log, labels, branches, tags.
|
|
3
|
+
|
|
4
|
+
Each command should exit 0 and include the repo directory names in its output.
|
|
5
|
+
Output contains ANSI colour codes but plain text like "repo_a" is always present.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from helpers import run_mud
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_no_args_shows_help(tmp_path: Path, home: Path):
|
|
12
|
+
"""mud with no arguments must print help without raising an exception (issue #90).
|
|
13
|
+
|
|
14
|
+
This crashed on Python 3.12 with an AssertionError inside argparse._format_usage
|
|
15
|
+
because the auto-generated usage string was too long and triggered a buggy
|
|
16
|
+
regex-based wrapping assertion. The fix is to supply a custom usage= string
|
|
17
|
+
to ArgumentParser so that assertion is never reached.
|
|
18
|
+
"""
|
|
19
|
+
result = run_mud(cwd=tmp_path, home=home)
|
|
20
|
+
assert result.returncode == 0, f"mud with no args exited {result.returncode}:\n{result.stderr}"
|
|
21
|
+
# Should mention key commands in the help output
|
|
22
|
+
assert "mud" in result.stdout or "mud" in result.stderr
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_status(repos: Path, home: Path):
|
|
26
|
+
result = run_mud("status", cwd=repos, home=home)
|
|
27
|
+
assert result.returncode == 0
|
|
28
|
+
assert "repo_a" in result.stdout
|
|
29
|
+
assert "repo_b" in result.stdout
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_info(repos: Path, home: Path):
|
|
33
|
+
result = run_mud("info", cwd=repos, home=home)
|
|
34
|
+
assert result.returncode == 0
|
|
35
|
+
assert "repo_a" in result.stdout
|
|
36
|
+
assert "repo_b" in result.stdout
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_log(repos: Path, home: Path):
|
|
40
|
+
result = run_mud("log", cwd=repos, home=home)
|
|
41
|
+
assert result.returncode == 0
|
|
42
|
+
assert "repo_a" in result.stdout
|
|
43
|
+
assert "repo_b" in result.stdout
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_labels(repos: Path, home: Path):
|
|
47
|
+
result = run_mud("labels", cwd=repos, home=home)
|
|
48
|
+
assert result.returncode == 0
|
|
49
|
+
assert "repo_a" in result.stdout
|
|
50
|
+
assert "repo_b" in result.stdout
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_labels_shows_label_values(repos_labeled: Path, home: Path):
|
|
54
|
+
result = run_mud("labels", cwd=repos_labeled, home=home)
|
|
55
|
+
assert result.returncode == 0
|
|
56
|
+
assert "label_a" in result.stdout
|
|
57
|
+
assert "label_b" in result.stdout
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_branches(repos: Path, home: Path):
|
|
61
|
+
result = run_mud("branches", cwd=repos, home=home)
|
|
62
|
+
assert result.returncode == 0
|
|
63
|
+
assert "repo_a" in result.stdout
|
|
64
|
+
assert "repo_b" in result.stdout
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_tags(repos: Path, home: Path):
|
|
68
|
+
result = run_mud("tags", cwd=repos, home=home)
|
|
69
|
+
assert result.returncode == 0
|
|
70
|
+
assert "repo_a" in result.stdout
|
|
71
|
+
assert "repo_b" in result.stdout
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for filter flags: -l= (include label), -L= (exclude label), -b= (branch).
|
|
3
|
+
|
|
4
|
+
All shell commands here use -a to run in ordered mode so the output is
|
|
5
|
+
straightforward to assert on.
|
|
6
|
+
"""
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from helpers import run_mud
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Label filters
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
def test_label_include_filter(repos_labeled: Path, home: Path):
|
|
17
|
+
"""-l=<label> restricts execution to repos that carry that label."""
|
|
18
|
+
result = run_mud("-a", "-l=label_a", "echo", "hello", cwd=repos_labeled, home=home)
|
|
19
|
+
assert result.returncode == 0
|
|
20
|
+
assert "repo_a" in result.stdout
|
|
21
|
+
assert "repo_b" not in result.stdout
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_label_exclude_filter(repos_labeled: Path, home: Path):
|
|
25
|
+
"""-L=<label> skips repos that carry that label."""
|
|
26
|
+
result = run_mud("-a", "-L=label_a", "echo", "hello", cwd=repos_labeled, home=home)
|
|
27
|
+
assert result.returncode == 0
|
|
28
|
+
assert "repo_b" in result.stdout
|
|
29
|
+
assert "repo_a" not in result.stdout
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Branch filter
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
def test_name_filter(repos: Path, home: Path):
|
|
37
|
+
"""-n=<string> restricts execution to repos whose path contains that string."""
|
|
38
|
+
result = run_mud("-a", "-n=repo_a", "echo", "hello", cwd=repos, home=home)
|
|
39
|
+
assert result.returncode == 0
|
|
40
|
+
assert "repo_a" in result.stdout
|
|
41
|
+
assert "repo_b" not in result.stdout
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_branch_filter(repos_labeled: Path, home: Path):
|
|
45
|
+
"""-b=<branch> restricts execution to repos currently on that branch."""
|
|
46
|
+
# Put repo_a on a feature branch; repo_b stays on the default branch.
|
|
47
|
+
subprocess.run(
|
|
48
|
+
["git", "checkout", "-b", "feature"],
|
|
49
|
+
cwd=repos_labeled / "repo_a",
|
|
50
|
+
check=True,
|
|
51
|
+
capture_output=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
result = run_mud("-a", "-b=feature", "echo", "hello", cwd=repos_labeled, home=home)
|
|
55
|
+
assert result.returncode == 0
|
|
56
|
+
assert "repo_a" in result.stdout
|
|
57
|
+
assert "repo_b" not in result.stdout
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for shell command execution modes.
|
|
3
|
+
|
|
4
|
+
mud has three execution modes controlled by the -a (async) and -t (table) flags.
|
|
5
|
+
Default settings have both enabled, so:
|
|
6
|
+
|
|
7
|
+
mud <cmd> -> async + table (run_async_table_view)
|
|
8
|
+
mud -t <cmd> -> async, no table (run_async)
|
|
9
|
+
mud -a <cmd> -> no async, no table (run_ordered) <- most readable output
|
|
10
|
+
|
|
11
|
+
We test that all modes exit 0 and that the command actually ran in each repo.
|
|
12
|
+
"""
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from helpers import run_mud
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_run_ordered(repos: Path, home: Path):
|
|
18
|
+
"""mud -a <cmd> runs sequentially, one repo at a time."""
|
|
19
|
+
result = run_mud("-a", "echo", "hello", cwd=repos, home=home)
|
|
20
|
+
assert result.returncode == 0
|
|
21
|
+
assert "hello" in result.stdout
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_run_async(repos: Path, home: Path):
|
|
25
|
+
"""mud -t <cmd> runs all repos concurrently without the table view."""
|
|
26
|
+
result = run_mud("-t", "echo", "hello", cwd=repos, home=home)
|
|
27
|
+
assert result.returncode == 0
|
|
28
|
+
assert "hello" in result.stdout
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_run_async_table_view(repos: Path, home: Path):
|
|
32
|
+
"""mud <cmd> uses the live table view by default."""
|
|
33
|
+
result = run_mud("echo", "hello", cwd=repos, home=home)
|
|
34
|
+
assert result.returncode == 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_run_explicit_command_flag(repos: Path, home: Path):
|
|
38
|
+
"""-c= lets you pass a command that contains spaces or special characters."""
|
|
39
|
+
result = run_mud("-a", "-c=echo hello", cwd=repos, home=home)
|
|
40
|
+
assert result.returncode == 0
|
|
41
|
+
assert "hello" in result.stdout
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_run_double_dash_separator(repos: Path, home: Path):
|
|
45
|
+
"""mud -t -- <cmd> treats everything after -- as the literal command."""
|
|
46
|
+
result = run_mud("-t", "--", "echo", "hello", cwd=repos, home=home)
|
|
47
|
+
assert result.returncode == 0
|
|
48
|
+
assert "hello" in result.stdout
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_run_command_runs_in_every_repo(repos: Path, home: Path):
|
|
52
|
+
"""The command is executed once per repo in the config."""
|
|
53
|
+
result = run_mud("-a", "echo", "hello", cwd=repos, home=home)
|
|
54
|
+
# "hello" appears at least once per repo (the header also echoes the command name)
|
|
55
|
+
assert result.stdout.count("hello") >= 2
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for display commands against repos in unusual states.
|
|
3
|
+
|
|
4
|
+
mud must handle every state gracefully — exit 0, no crashes.
|
|
5
|
+
The six display commands are run against four edge-case repo states:
|
|
6
|
+
|
|
7
|
+
no commits — git init only, HEAD is unborn
|
|
8
|
+
detached — HEAD points at a commit hash, not a branch
|
|
9
|
+
on tag — HEAD detached at a tagged commit
|
|
10
|
+
rebasing — rebase stopped mid-way due to a conflict
|
|
11
|
+
"""
|
|
12
|
+
import pytest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from helpers import (
|
|
15
|
+
run_mud,
|
|
16
|
+
make_empty_repo,
|
|
17
|
+
make_detached_repo,
|
|
18
|
+
make_tagged_repo,
|
|
19
|
+
make_rebasing_repo,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DISPLAY_COMMANDS = ["status", "info", "log", "labels", "branches", "tags"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Fixtures — one per repo state
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def repo_no_commits(tmp_path: Path) -> Path:
|
|
31
|
+
make_empty_repo(tmp_path / "repo_a")
|
|
32
|
+
(tmp_path / ".mudconfig").write_text("repo_a\t\n")
|
|
33
|
+
return tmp_path
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def repo_detached(tmp_path: Path) -> Path:
|
|
38
|
+
make_detached_repo(tmp_path / "repo_a")
|
|
39
|
+
(tmp_path / ".mudconfig").write_text("repo_a\t\n")
|
|
40
|
+
return tmp_path
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def repo_on_tag(tmp_path: Path) -> Path:
|
|
45
|
+
make_tagged_repo(tmp_path / "repo_a")
|
|
46
|
+
(tmp_path / ".mudconfig").write_text("repo_a\t\n")
|
|
47
|
+
return tmp_path
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def repo_rebasing(tmp_path: Path) -> Path:
|
|
52
|
+
make_rebasing_repo(tmp_path / "repo_a")
|
|
53
|
+
(tmp_path / ".mudconfig").write_text("repo_a\t\n")
|
|
54
|
+
return tmp_path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Tests — parametrised over every display command
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize("cmd", DISPLAY_COMMANDS)
|
|
62
|
+
def test_display_no_commits(repo_no_commits: Path, home: Path, cmd: str):
|
|
63
|
+
"""Display commands must not crash on a repo with no commits."""
|
|
64
|
+
result = run_mud(cmd, cwd=repo_no_commits, home=home)
|
|
65
|
+
assert result.returncode == 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.parametrize("cmd", DISPLAY_COMMANDS)
|
|
69
|
+
def test_display_detached_head(repo_detached: Path, home: Path, cmd: str):
|
|
70
|
+
"""Display commands must not crash on a repo with detached HEAD."""
|
|
71
|
+
result = run_mud(cmd, cwd=repo_detached, home=home)
|
|
72
|
+
assert result.returncode == 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize("cmd", DISPLAY_COMMANDS)
|
|
76
|
+
def test_display_on_tag(repo_on_tag: Path, home: Path, cmd: str):
|
|
77
|
+
"""Display commands must not crash on a repo checked out at a tag."""
|
|
78
|
+
result = run_mud(cmd, cwd=repo_on_tag, home=home)
|
|
79
|
+
assert result.returncode == 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize("cmd", DISPLAY_COMMANDS)
|
|
83
|
+
def test_display_rebasing(repo_rebasing: Path, home: Path, cmd: str):
|
|
84
|
+
"""Display commands must not crash on a repo that is mid-rebase."""
|
|
85
|
+
result = run_mud(cmd, cwd=repo_rebasing, home=home)
|
|
86
|
+
assert result.returncode == 0
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Upload Python Package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
deploy:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v4
|
|
17
|
-
- name: Set up Python
|
|
18
|
-
uses: actions/setup-python@v3
|
|
19
|
-
with:
|
|
20
|
-
python-version: '3.x'
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
run: |
|
|
23
|
-
python -m pip install --upgrade pip
|
|
24
|
-
pip install build
|
|
25
|
-
- name: Build package
|
|
26
|
-
run: python -m build
|
|
27
|
-
- name: Publish package
|
|
28
|
-
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
|
29
|
-
with:
|
|
30
|
-
user: __token__
|
|
31
|
-
password: ${{ secrets.TWINE_PAT }}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
name: Test
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- '*'
|
|
7
|
-
pull_request:
|
|
8
|
-
branches:
|
|
9
|
-
- master
|
|
10
|
-
workflow_dispatch:
|
|
11
|
-
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
|
|
15
|
-
jobs:
|
|
16
|
-
test:
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
- name: Set up Python 3.13
|
|
21
|
-
uses: actions/setup-python@v3
|
|
22
|
-
with:
|
|
23
|
-
python-version: "3.13"
|
|
24
|
-
- name: Install dependencies and set up the environment
|
|
25
|
-
run: |
|
|
26
|
-
python -m pip install -r requirements.txt
|
|
27
|
-
python -m pip install requests build
|
|
28
|
-
|
|
29
|
-
python -m build
|
|
30
|
-
pip install dist/*.whl
|
|
31
|
-
- name: Create repositories
|
|
32
|
-
run: |
|
|
33
|
-
git config --global user.name "Jasur Sadikov"
|
|
34
|
-
git config --global user.email "test@test.com"
|
|
35
|
-
|
|
36
|
-
for i in {1..3}; do
|
|
37
|
-
mkdir "repo_$i"
|
|
38
|
-
cd "repo_$i"
|
|
39
|
-
echo "repo_$i" > test.txt
|
|
40
|
-
git init
|
|
41
|
-
git add .
|
|
42
|
-
git commit -m "Initial commit"
|
|
43
|
-
cd ..
|
|
44
|
-
done
|
|
45
|
-
- name: Initialize
|
|
46
|
-
run: |
|
|
47
|
-
echo "mud init"
|
|
48
|
-
mud init
|
|
49
|
-
- name: Test remove by path
|
|
50
|
-
run: |
|
|
51
|
-
for i in {1..3}; do
|
|
52
|
-
echo "mud remove repo_$i"
|
|
53
|
-
mud remove repo_$i
|
|
54
|
-
done
|
|
55
|
-
- name: Test add with label
|
|
56
|
-
run: |
|
|
57
|
-
for i in {1..3}; do
|
|
58
|
-
echo "mud add repo_$i label_$i"
|
|
59
|
-
mud add repo_$i label_$i
|
|
60
|
-
done
|
|
61
|
-
echo "mud labels"
|
|
62
|
-
- name: Test remove path
|
|
63
|
-
run: |
|
|
64
|
-
for i in {1..3}; do
|
|
65
|
-
echo "mud remove repo_$i"
|
|
66
|
-
mud remove repo_$i
|
|
67
|
-
done
|
|
68
|
-
echo "mud labels"
|
|
69
|
-
- name: Test prune command
|
|
70
|
-
run: |
|
|
71
|
-
mud init
|
|
72
|
-
echo "test" > .mudconfig
|
|
73
|
-
echo "mud prune"
|
|
74
|
-
echo -e "test1_remove\ntest2_remove" > .mudconfig
|
|
75
|
-
mud prune
|
|
76
|
-
- name: Add all repositories with labels
|
|
77
|
-
run: |
|
|
78
|
-
rm .mudconfig
|
|
79
|
-
mud init
|
|
80
|
-
for i in {1..3}; do
|
|
81
|
-
mud add label_$i repo_$i
|
|
82
|
-
done
|
|
83
|
-
- name: Test default commands
|
|
84
|
-
run: |
|
|
85
|
-
cat .mudconfig
|
|
86
|
-
echo "mud labels"
|
|
87
|
-
mud labels
|
|
88
|
-
echo "mud status"
|
|
89
|
-
mud status
|
|
90
|
-
echo "mud info"
|
|
91
|
-
mud info
|
|
92
|
-
echo "mud log"
|
|
93
|
-
mud log
|
|
94
|
-
echo "mud branch"
|
|
95
|
-
mud branch
|
|
96
|
-
echo "mud tags"
|
|
97
|
-
mud tags
|
|
98
|
-
- name: Test custom command
|
|
99
|
-
run: |
|
|
100
|
-
mud echo "Hello world"
|
|
101
|
-
mud -a echo "Hello world"
|
|
102
|
-
mud -a -t echo "Hello world"
|
|
103
|
-
mud -t echo "Hello world"
|
|
104
|
-
mud -t -c='echo "Hello" && echo "world"'
|
|
105
|
-
- name: Test label filtering
|
|
106
|
-
run: mud -l=label_1 echo "Hello world"
|
|
107
|
-
- name: Test branch filtering
|
|
108
|
-
run: |
|
|
109
|
-
mud -l=label_1 git checkout -b develop
|
|
110
|
-
mud -b=develop echo "Hello world"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|