git-nested 1.1.0__tar.gz → 1.1.1__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.
- {git_nested-1.1.0 → git_nested-1.1.1}/.fish.rc +4 -3
- git_nested-1.1.1/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- git_nested-1.1.1/.github/ISSUE_TEMPLATE/config.yml +8 -0
- git_nested-1.1.1/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- git_nested-1.1.1/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- git_nested-1.1.1/.github/dependabot.yml +23 -0
- git_nested-1.1.1/.github/release.yml +22 -0
- git_nested-1.1.1/.github/workflows/build-binary.yml +137 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/.github/workflows/publish.yml +3 -3
- {git_nested-1.1.0 → git_nested-1.1.1}/.github/workflows/release-binary.yml +41 -19
- {git_nested-1.1.0 → git_nested-1.1.1}/.github/workflows/test-binary.yml +6 -6
- {git_nested-1.1.0 → git_nested-1.1.1}/.github/workflows/test.yml +40 -11
- {git_nested-1.1.0 → git_nested-1.1.1}/.gitignore +1 -1
- {git_nested-1.1.0 → git_nested-1.1.1}/.pre-commit-config.yaml +5 -4
- {git_nested-1.1.0 → git_nested-1.1.1}/.rc +8 -2
- git_nested-1.1.1/CONTRIBUTING.md +95 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/PKG-INFO +145 -102
- {git_nested-1.1.0 → git_nested-1.1.1}/README.md +141 -98
- git_nested-1.1.1/SECURITY.md +34 -0
- git_nested-1.1.1/bin/git-nested +39 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/docs/diagrams.md +113 -5
- git_nested-1.1.1/man/man1/git-nested.1 +138 -0
- git_nested-1.1.1/pyproject.toml +221 -0
- git_nested-1.1.1/scripts/create-python-exe.sh +257 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/sonar-project.properties +3 -3
- git_nested-1.1.1/src/git_nested/__init__.py +40 -0
- git_nested-1.1.1/src/git_nested/__main__.py +10 -0
- git_nested-1.1.1/src/git_nested/_compat.py +32 -0
- git_nested-1.1.1/src/git_nested/_version.py +21 -0
- git_nested-1.1.1/src/git_nested/checks.py +104 -0
- git_nested-1.1.1/src/git_nested/cli/__init__.py +3 -0
- git_nested-1.1.1/src/git_nested/cli/app.py +109 -0
- git_nested-1.1.1/src/git_nested/cli/main.py +20 -0
- git_nested-1.1.1/src/git_nested/cli/parser.py +170 -0
- git_nested-1.1.1/src/git_nested/cli/setup.py +103 -0
- git_nested-1.1.1/src/git_nested/cli/spec.py +243 -0
- git_nested-1.1.1/src/git_nested/commands/__init__.py +49 -0
- git_nested-1.1.1/src/git_nested/commands/branch.py +37 -0
- git_nested-1.1.1/src/git_nested/commands/clean.py +55 -0
- git_nested-1.1.1/src/git_nested/commands/clone.py +160 -0
- git_nested-1.1.1/src/git_nested/commands/commit.py +46 -0
- git_nested-1.1.1/src/git_nested/commands/completion.py +14 -0
- git_nested-1.1.1/src/git_nested/commands/config.py +104 -0
- git_nested-1.1.1/src/git_nested/commands/diff.py +53 -0
- git_nested-1.1.1/src/git_nested/commands/fetch.py +49 -0
- git_nested-1.1.1/src/git_nested/commands/init.py +81 -0
- git_nested-1.1.1/src/git_nested/commands/pull.py +234 -0
- git_nested-1.1.1/src/git_nested/commands/push.py +329 -0
- git_nested-1.1.1/src/git_nested/commands/status.py +184 -0
- git_nested-1.1.1/src/git_nested/commands/version.py +23 -0
- git_nested-1.1.1/src/git_nested/completion/__init__.py +278 -0
- git_nested-1.1.1/src/git_nested/completion/scripts.py +210 -0
- git_nested-1.1.1/src/git_nested/constants.py +13 -0
- git_nested-1.1.1/src/git_nested/content.py +199 -0
- git_nested-1.1.1/src/git_nested/discovery.py +76 -0
- git_nested-1.1.1/src/git_nested/errors.py +18 -0
- git_nested-1.1.1/src/git_nested/filters.py +164 -0
- git_nested-1.1.1/src/git_nested/git.py +104 -0
- git_nested-1.1.1/src/git_nested/gitfile.py +194 -0
- git_nested-1.1.1/src/git_nested/history.py +224 -0
- git_nested-1.1.1/src/git_nested/messages.py +95 -0
- git_nested-1.1.1/src/git_nested/models.py +114 -0
- git_nested-1.1.1/src/git_nested/output.py +172 -0
- git_nested-1.1.1/src/git_nested/py.typed +0 -0
- git_nested-1.1.1/src/git_nested/refs.py +58 -0
- git_nested-1.1.1/src/git_nested/repo.py +56 -0
- git_nested-1.1.1/src/git_nested/worktree.py +40 -0
- git_nested-1.1.1/src/git_nested/yamlio.py +22 -0
- {git_nested-1.1.0/lib → git_nested-1.1.1/src}/git_nested.egg-info/PKG-INFO +145 -102
- git_nested-1.1.1/src/git_nested.egg-info/SOURCES.txt +113 -0
- git_nested-1.1.1/src/git_nested.egg-info/requires.txt +1 -0
- git_nested-1.1.1/src/git_nested.egg-info/scm_file_list.json +109 -0
- git_nested-1.1.1/src/git_nested.egg-info/scm_version.json +8 -0
- {git_nested-1.1.0/test → git_nested-1.1.1/tests}/conftest.py +102 -35
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/scenarios/test_extraction.py +5 -6
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/scenarios/test_filter.py +2 -1
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_branch.py +28 -19
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_clean.py +1 -1
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_clone.py +18 -20
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_commit.py +2 -2
- git_nested-1.1.1/tests/e2e/test_completion.py +201 -0
- git_nested-1.1.1/tests/e2e/test_config.py +125 -0
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_diff.py +5 -13
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_encode.py +4 -5
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_error.py +28 -30
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_fetch.py +2 -3
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_gitignore.py +1 -1
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_init.py +11 -13
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_nested_in_nested.py +6 -16
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_pull.py +32 -41
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_push.py +26 -28
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_rebase.py +9 -7
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_reclone.py +3 -5
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_status.py +3 -2
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/test_submodule.py +1 -1
- git_nested-1.1.1/tests/e2e/test_version.py +28 -0
- git_nested-1.1.1/tests/unit/conftest.py +37 -0
- git_nested-1.1.1/tests/unit/fakes.py +78 -0
- {git_nested-1.1.0/test/unittest → git_nested-1.1.1/tests/unit}/test_command.py +33 -26
- git_nested-1.1.1/tests/unit/test_completion_engine.py +321 -0
- git_nested-1.1.1/tests/unit/test_docs.py +39 -0
- git_nested-1.1.1/tests/unit/test_facade.py +97 -0
- {git_nested-1.1.0/test/unittest → git_nested-1.1.1/tests/unit}/test_helpers.py +78 -47
- git_nested-1.1.1/tests/unit/test_logging.py +193 -0
- {git_nested-1.1.0/test/unittest → git_nested-1.1.1/tests/unit}/test_repo_business.py +70 -93
- {git_nested-1.1.0 → git_nested-1.1.1}/uv.lock +83 -829
- git_nested-1.1.0/.github/workflows/build-binary.yml +0 -78
- git_nested-1.1.0/CONTRIBUTING.md +0 -229
- git_nested-1.1.0/lib/git-nested +0 -13
- git_nested-1.1.0/lib/git_nested.egg-info/SOURCES.txt +0 -60
- git_nested-1.1.0/lib/git_nested.egg-info/requires.txt +0 -1
- git_nested-1.1.0/lib/git_nested.py +0 -2690
- git_nested-1.1.0/pyproject.toml +0 -156
- git_nested-1.1.0/scripts/create-python-exe.sh +0 -189
- git_nested-1.1.0/share/completion.bash +0 -44
- git_nested-1.1.0/share/enable-completion.sh +0 -35
- git_nested-1.1.0/share/git-nested.fish +0 -35
- git_nested-1.1.0/share/zsh-completion/_git-nested +0 -72
- git_nested-1.1.0/test/test_completion.py +0 -144
- git_nested-1.1.0/test/test_version.py +0 -13
- git_nested-1.1.0/test/unittest/conftest.py +0 -62
- git_nested-1.1.0/test/unittest/test_logging.py +0 -159
- {git_nested-1.1.0 → git_nested-1.1.1}/.codecov.yml +0 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/.gitattributes +0 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/.github/CODEOWNERS +0 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/LICENSE +0 -0
- {git_nested-1.1.0 → git_nested-1.1.1}/setup.cfg +0 -0
- {git_nested-1.1.0/lib → git_nested-1.1.1/src}/git_nested.egg-info/dependency_links.txt +0 -0
- {git_nested-1.1.0/lib → git_nested-1.1.1/src}/git_nested.egg-info/entry_points.txt +0 -0
- {git_nested-1.1.0/lib → git_nested-1.1.1/src}/git_nested.egg-info/top_level.txt +0 -0
- {git_nested-1.1.0/test → git_nested-1.1.1/tests/e2e}/scenarios/test_filemode.py +0 -0
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
#------------------------------------------------------------------------------
|
|
12
12
|
|
|
13
13
|
set GIT_NESTED_ROOT (dirname (realpath (status --current-filename)))
|
|
14
|
-
set PATH $GIT_NESTED_ROOT/
|
|
14
|
+
set PATH $GIT_NESTED_ROOT/bin $PATH
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Tab completion. The script is generated by git-nested itself, from the
|
|
17
|
+
# same tables the argument parser is built from, so it can never drift.
|
|
18
|
+
git-nested completion fish | source
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something git-nested does that it should not
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What happened
|
|
8
|
+
|
|
9
|
+
<!-- Paste the command and its output. `-v` or `-d` output helps a lot. -->
|
|
10
|
+
|
|
11
|
+
## What you expected instead
|
|
12
|
+
|
|
13
|
+
## How to reproduce
|
|
14
|
+
|
|
15
|
+
<!-- The shortest sequence of commands that shows it, starting from an empty
|
|
16
|
+
repository if you can. -->
|
|
17
|
+
|
|
18
|
+
## Versions
|
|
19
|
+
|
|
20
|
+
- git-nested: <!-- git nested --version -->
|
|
21
|
+
- git: <!-- git --version -->
|
|
22
|
+
- OS:
|
|
23
|
+
- Installed via: <!-- pip / standalone binary / source .rc -->
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Question or idea
|
|
4
|
+
url: https://github.com/thorsten-klein/git-nested/discussions
|
|
5
|
+
about: For anything that is not a bug report or a concrete feature request.
|
|
6
|
+
- name: Security vulnerability
|
|
7
|
+
url: https://github.com/thorsten-klein/git-nested/security/advisories/new
|
|
8
|
+
about: Report privately here, not as a public issue.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Something git-nested cannot do yet
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What you are trying to do
|
|
8
|
+
|
|
9
|
+
<!-- The workflow, not the feature. It often turns out there is already a way. -->
|
|
10
|
+
|
|
11
|
+
## What you would like git-nested to do
|
|
12
|
+
|
|
13
|
+
<!-- If you have a command line in mind, write it out. -->
|
|
14
|
+
|
|
15
|
+
## What you do instead today
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## What this changes
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. What is different after this is merged? -->
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
<!-- The problem it solves. Link the issue if there is one: Fixes #123 -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] `uv run poe all` passes (ruff, mypy, pyright, bandit, complexipy, and the
|
|
12
|
+
tests at 100% coverage)
|
|
13
|
+
- [ ] New behaviour has a test
|
|
14
|
+
- [ ] README / `--help` / the man page updated, if the change is visible to users
|
|
15
|
+
- [ ] The PR title reads as a [Conventional Commit](https://www.conventionalcommits.org)
|
|
16
|
+
(`feat:`, `fix:`, `docs:`, …) — it becomes the squash-merge commit message
|
|
17
|
+
and the changelog line
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Dependabot keeps the two things this repository pins up to date: the actions
|
|
2
|
+
# every workflow runs, and the Python dependencies uv.lock records.
|
|
3
|
+
version: 2
|
|
4
|
+
updates:
|
|
5
|
+
- package-ecosystem: github-actions
|
|
6
|
+
directory: /
|
|
7
|
+
schedule:
|
|
8
|
+
interval: weekly
|
|
9
|
+
groups:
|
|
10
|
+
# One pull request for the lot. Action bumps are almost always routine,
|
|
11
|
+
# and reviewing them together is quicker than reviewing them one by one.
|
|
12
|
+
actions:
|
|
13
|
+
patterns: ['*']
|
|
14
|
+
|
|
15
|
+
- package-ecosystem: uv
|
|
16
|
+
directory: /
|
|
17
|
+
schedule:
|
|
18
|
+
interval: weekly
|
|
19
|
+
groups:
|
|
20
|
+
# Dev tooling moves often and breaks nothing users see, so it travels
|
|
21
|
+
# together; a runtime dependency gets its own pull request.
|
|
22
|
+
dev:
|
|
23
|
+
dependency-type: development
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Categories for the notes GitHub generates from merged pull requests. The
|
|
2
|
+
# repository squash-merges with Conventional Commit titles, so the labels
|
|
3
|
+
# below are what sorts a PR into a section -- keep them on the PR, not on the
|
|
4
|
+
# commit.
|
|
5
|
+
changelog:
|
|
6
|
+
exclude:
|
|
7
|
+
labels: [skip-changelog]
|
|
8
|
+
categories:
|
|
9
|
+
- title: Breaking changes
|
|
10
|
+
labels: [breaking]
|
|
11
|
+
- title: Features
|
|
12
|
+
labels: [enhancement, feature]
|
|
13
|
+
- title: Fixes
|
|
14
|
+
labels: [bug, fix]
|
|
15
|
+
- title: Documentation
|
|
16
|
+
labels: [documentation]
|
|
17
|
+
- title: Build, CI and tooling
|
|
18
|
+
labels: [build, ci, tooling]
|
|
19
|
+
- title: Dependencies
|
|
20
|
+
labels: [dependencies]
|
|
21
|
+
- title: Other changes
|
|
22
|
+
labels: ['*']
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
name: Build binary
|
|
2
|
+
|
|
3
|
+
# Builds the standalone 'git-nested' executable for one target and uploads it
|
|
4
|
+
# as an artifact. Called by test.yml (so every push/PR proves the recipe still
|
|
5
|
+
# builds) and by release-binary.yml (which attaches it to the release) -- so
|
|
6
|
+
# what CI exercises is built by exactly the recipe that ships, rather than a
|
|
7
|
+
# lookalike that can drift away from it.
|
|
8
|
+
#
|
|
9
|
+
# One call builds one target: PyInstaller freezes for the machine it runs on
|
|
10
|
+
# and nothing else, so Linux, macOS and Windows binaries come from three calls
|
|
11
|
+
# with different 'runs-on'/'container', not from one job.
|
|
12
|
+
on:
|
|
13
|
+
workflow_call:
|
|
14
|
+
inputs:
|
|
15
|
+
ref:
|
|
16
|
+
description: 'Commit/tag/branch to build. Empty: whatever triggered the caller.'
|
|
17
|
+
type: string
|
|
18
|
+
required: false
|
|
19
|
+
default: ''
|
|
20
|
+
artifact:
|
|
21
|
+
description: 'Name of the uploaded artifact'
|
|
22
|
+
type: string
|
|
23
|
+
required: false
|
|
24
|
+
default: git-nested-binary
|
|
25
|
+
runs-on:
|
|
26
|
+
description: 'Runner to build on -- ubuntu-latest, macos-latest, windows-latest'
|
|
27
|
+
type: string
|
|
28
|
+
required: false
|
|
29
|
+
default: ubuntu-latest
|
|
30
|
+
container:
|
|
31
|
+
description: 'Container to build inside (Linux only). Empty: build on the runner itself.'
|
|
32
|
+
type: string
|
|
33
|
+
required: false
|
|
34
|
+
default: almalinux:8
|
|
35
|
+
python-version:
|
|
36
|
+
description: 'Python to freeze, e.g. 3.12'
|
|
37
|
+
type: string
|
|
38
|
+
required: false
|
|
39
|
+
default: '3.12'
|
|
40
|
+
outputs:
|
|
41
|
+
archive:
|
|
42
|
+
description: 'File name of the built archive, e.g. git-nested-1.2.3-x64-linux.tar.xz. Only meaningful for a single (non-matrix) call.'
|
|
43
|
+
value: ${{ jobs.build.outputs.archive }}
|
|
44
|
+
version:
|
|
45
|
+
description: 'Version of the built git-nested, e.g. 1.2.3'
|
|
46
|
+
value: ${{ jobs.build.outputs.version }}
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
build:
|
|
50
|
+
runs-on: ${{ inputs.runs-on }}
|
|
51
|
+
# An empty string here means "no container", which is what the macOS and
|
|
52
|
+
# Windows callers pass -- containers are a Linux-runner feature.
|
|
53
|
+
#
|
|
54
|
+
# For Linux the default is almalinux:8, and that is a portability
|
|
55
|
+
# decision, not a convenience one: a PyInstaller binary links the glibc of
|
|
56
|
+
# the machine that built it, and glibc is only backward compatible, so
|
|
57
|
+
# built on ubuntu-latest it would refuse to start on anything older than
|
|
58
|
+
# that runner. AlmaLinux 8 has glibc 2.28, the oldest that still runs the
|
|
59
|
+
# node20 the actions below need, which covers every distro from Ubuntu
|
|
60
|
+
# 20.04 / Debian 10 / Fedora 29 / RHEL 8 onwards. glibc-based distros only
|
|
61
|
+
# -- musl ones (Alpine) can never run this binary.
|
|
62
|
+
container: ${{ inputs.container }}
|
|
63
|
+
# bash everywhere, including Windows, where the default is pwsh --
|
|
64
|
+
# create-python-exe.sh is one script for all three targets.
|
|
65
|
+
defaults:
|
|
66
|
+
run:
|
|
67
|
+
shell: bash
|
|
68
|
+
outputs:
|
|
69
|
+
archive: ${{ steps.build.outputs.archive }}
|
|
70
|
+
version: ${{ steps.build.outputs.version }}
|
|
71
|
+
steps:
|
|
72
|
+
# Before the checkout -- without git, actions/checkout silently falls
|
|
73
|
+
# back to downloading a tarball, and a checkout with no .git has no
|
|
74
|
+
# tags for setuptools-scm to derive the version from. Only the container
|
|
75
|
+
# needs this; a hosted runner ships all of it.
|
|
76
|
+
- name: Install build prerequisites
|
|
77
|
+
if: inputs.container != ''
|
|
78
|
+
run: dnf -y install "python${{ inputs.python-version }}" "python${{ inputs.python-version }}-pip" git tar xz
|
|
79
|
+
|
|
80
|
+
# ... and the runners get their python from here instead.
|
|
81
|
+
- uses: actions/setup-python@v7
|
|
82
|
+
if: inputs.container == ''
|
|
83
|
+
with:
|
|
84
|
+
python-version: ${{ inputs.python-version }}
|
|
85
|
+
|
|
86
|
+
- uses: actions/checkout@v7
|
|
87
|
+
with:
|
|
88
|
+
ref: ${{ inputs.ref }}
|
|
89
|
+
# the version frozen into the binary comes from the tags, and the
|
|
90
|
+
# default shallow checkout fetches none of them.
|
|
91
|
+
fetch-depth: 0
|
|
92
|
+
|
|
93
|
+
# the checkout is owned by a different user than this container's root,
|
|
94
|
+
# which git refuses to read from until told it is not a security issue.
|
|
95
|
+
- name: Trust the checkout
|
|
96
|
+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
97
|
+
|
|
98
|
+
- name: Build the executable
|
|
99
|
+
id: build
|
|
100
|
+
run: |
|
|
101
|
+
# In the container python is installed under its versioned name; on
|
|
102
|
+
# a runner setup-python puts a plain 'python' on PATH.
|
|
103
|
+
if [ -n "${{ inputs.container }}" ]; then
|
|
104
|
+
PY="python${{ inputs.python-version }}"
|
|
105
|
+
else
|
|
106
|
+
PY=python
|
|
107
|
+
fi
|
|
108
|
+
scripts/create-python-exe.sh --output dist --python "$PY"
|
|
109
|
+
# The archive name carries the version and the target
|
|
110
|
+
# (git-nested-<version>-<arch>-<os>.tar.xz, .zip on Windows), so it
|
|
111
|
+
# cannot be spelled out ahead of time -- it is published as an
|
|
112
|
+
# output for callers that need the exact name.
|
|
113
|
+
#
|
|
114
|
+
# nullglob: only one of the two patterns ever matches (a build
|
|
115
|
+
# produces .tar.xz or .zip, never both) -- without it the unmatched
|
|
116
|
+
# pattern is passed to ls literally, ls exits non-zero trying to
|
|
117
|
+
# read it, and pipefail+errexit abort the step right here with no
|
|
118
|
+
# visible error (ls's message went to /dev/null).
|
|
119
|
+
shopt -s nullglob
|
|
120
|
+
ARCHIVE="$(ls dist/*.tar.xz dist/*.zip 2>/dev/null | head -1)"
|
|
121
|
+
test -n "$ARCHIVE" || { echo "no archive produced" >&2; exit 1; }
|
|
122
|
+
echo "archive=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
|
123
|
+
# 'git-nested Version: X.Y.Z' -> X.Y.Z
|
|
124
|
+
EXE=dist/git-nested
|
|
125
|
+
[ -f "$EXE" ] || EXE=dist/git-nested.exe
|
|
126
|
+
echo "version=$("$EXE" --version | head -1 | cut -d' ' -f3)" >> "$GITHUB_OUTPUT"
|
|
127
|
+
|
|
128
|
+
# the archive rather than the bare executable. It is what a release
|
|
129
|
+
# actually ships, and on the two platforms where it is a .tar.xz it
|
|
130
|
+
# keeps the exec bit as well, so consumers just untar and run it.
|
|
131
|
+
- uses: actions/upload-artifact@v7
|
|
132
|
+
with:
|
|
133
|
+
name: ${{ inputs.artifact }}
|
|
134
|
+
path: |
|
|
135
|
+
dist/*.tar.xz
|
|
136
|
+
dist/*.zip
|
|
137
|
+
if-no-files-found: error
|
|
@@ -9,7 +9,7 @@ jobs:
|
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
11
|
- name: Checkout code
|
|
12
|
-
uses: actions/checkout@
|
|
12
|
+
uses: actions/checkout@v7
|
|
13
13
|
with:
|
|
14
14
|
fetch-depth: 0
|
|
15
15
|
- name: Install uv
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
- name: Build package
|
|
23
23
|
run: uv run poe build
|
|
24
24
|
- name: Upload dist
|
|
25
|
-
uses: actions/upload-artifact@
|
|
25
|
+
uses: actions/upload-artifact@v7
|
|
26
26
|
with:
|
|
27
27
|
name: dist
|
|
28
28
|
path: dist/
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
id-token: write
|
|
38
38
|
steps:
|
|
39
39
|
- name: Download dist
|
|
40
|
-
uses: actions/download-artifact@
|
|
40
|
+
uses: actions/download-artifact@v8
|
|
41
41
|
with:
|
|
42
42
|
name: dist
|
|
43
43
|
path: dist/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
name: Release binary
|
|
2
2
|
|
|
3
|
-
# Builds the single-file 'git-nested' executable
|
|
4
|
-
# scripts/create-python-exe.sh) and attaches
|
|
5
|
-
# git-nested
|
|
6
|
-
#
|
|
3
|
+
# Builds the single-file 'git-nested' executable for Linux, macOS and Windows
|
|
4
|
+
# (see scripts/create-python-exe.sh) and attaches each archive to the release,
|
|
5
|
+
# so git-nested can be used on a machine that has no python -- and no pip, no
|
|
6
|
+
# uv -- at all.
|
|
7
7
|
#
|
|
8
8
|
# Separate from publish.yml on purpose. That one is triggered by the tag push
|
|
9
9
|
# and uploads to PyPI, while attaching an asset needs the GitHub *release* to
|
|
@@ -40,10 +40,27 @@ jobs:
|
|
|
40
40
|
# test.yml so both exercise the same executable. 'env.REF' is not available
|
|
41
41
|
# to a reusable workflow's inputs, so the ref is spelled out again.
|
|
42
42
|
build:
|
|
43
|
+
strategy:
|
|
44
|
+
fail-fast: false
|
|
45
|
+
matrix:
|
|
46
|
+
include:
|
|
47
|
+
# Intel macOS is deliberately absent: those runners are gone, and
|
|
48
|
+
# the arm64 binary is what a current Mac needs.
|
|
49
|
+
- target: linux
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
container: almalinux:8
|
|
52
|
+
- target: macos
|
|
53
|
+
runs-on: macos-latest
|
|
54
|
+
container: ''
|
|
55
|
+
- target: windows
|
|
56
|
+
runs-on: windows-latest
|
|
57
|
+
container: ''
|
|
43
58
|
uses: ./.github/workflows/build-binary.yml
|
|
44
59
|
with:
|
|
45
60
|
ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
|
|
46
|
-
artifact: git-nested-binary
|
|
61
|
+
artifact: git-nested-binary-${{ matrix.target }}
|
|
62
|
+
runs-on: ${{ matrix.runs-on }}
|
|
63
|
+
container: ${{ matrix.container }}
|
|
47
64
|
|
|
48
65
|
# The whole pytest suite, driven through the binary that is about to be
|
|
49
66
|
# attached -- the check that it is functionally complete, not just startable.
|
|
@@ -52,7 +69,7 @@ jobs:
|
|
|
52
69
|
uses: ./.github/workflows/test-binary.yml
|
|
53
70
|
with:
|
|
54
71
|
ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
|
|
55
|
-
artifact: git-nested-binary
|
|
72
|
+
artifact: git-nested-binary-linux
|
|
56
73
|
|
|
57
74
|
# Proves the "runs on any x86_64 Linux" claim rather than assuming it. The
|
|
58
75
|
# binary is dropped into stock distro images that have no python installed,
|
|
@@ -81,14 +98,14 @@ jobs:
|
|
|
81
98
|
- image: archlinux:latest
|
|
82
99
|
install: pacman -Sy --noconfirm git
|
|
83
100
|
steps:
|
|
84
|
-
- uses: actions/download-artifact@
|
|
101
|
+
- uses: actions/download-artifact@v8
|
|
85
102
|
with:
|
|
86
|
-
name: git-nested-binary
|
|
103
|
+
name: git-nested-binary-linux
|
|
87
104
|
|
|
88
|
-
# Named git-nested-<version>-
|
|
89
|
-
# the version isn't known here.
|
|
105
|
+
# Named git-nested-<version>-<arch>-linux.tar.xz -- resolved by glob
|
|
106
|
+
# since the version isn't known here.
|
|
90
107
|
- name: Unpack
|
|
91
|
-
run: tar -xf git-nested-*-
|
|
108
|
+
run: tar -xf git-nested-*-linux.tar.xz
|
|
92
109
|
|
|
93
110
|
# git is the one thing that does have to be installed -- git-nested
|
|
94
111
|
# drives it and refuses to start without it. Python deliberately is not:
|
|
@@ -150,17 +167,22 @@ jobs:
|
|
|
150
167
|
permissions:
|
|
151
168
|
contents: write
|
|
152
169
|
steps:
|
|
153
|
-
|
|
170
|
+
# every target's artifact, unpacked side by side. 'build' is a matrix,
|
|
171
|
+
# and a matrix job's outputs are last-one-wins, so the exact file names
|
|
172
|
+
# are read off disk here rather than composed from an output.
|
|
173
|
+
- uses: actions/download-artifact@v8
|
|
154
174
|
with:
|
|
155
|
-
|
|
175
|
+
pattern: git-nested-binary-*
|
|
176
|
+
merge-multiple: true
|
|
156
177
|
|
|
157
178
|
# --clobber so re-running against an already-attached release replaces
|
|
158
|
-
# the
|
|
159
|
-
|
|
160
|
-
# release asset, so it uses the real version rather than a glob.
|
|
161
|
-
- name: Attach git-nested-${{ needs.build.outputs.version }}-x64-linux.tar.xz to ${{ env.REF }}
|
|
179
|
+
# the assets instead of failing halfway.
|
|
180
|
+
- name: Attach the binaries to ${{ env.REF }}
|
|
162
181
|
env:
|
|
163
182
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
164
183
|
GH_REPO: ${{ github.repository }}
|
|
165
|
-
|
|
166
|
-
|
|
184
|
+
run: |
|
|
185
|
+
mapfile -t assets < <(ls git-nested-*.tar.xz git-nested-*.zip 2>/dev/null)
|
|
186
|
+
test "${#assets[@]}" -gt 0 || { echo "no archives downloaded" >&2; exit 1; }
|
|
187
|
+
printf '%s\n' "${assets[@]}"
|
|
188
|
+
gh release upload "$REF" "${assets[@]}" --clobber
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
name: Test binary
|
|
2
2
|
|
|
3
|
-
# Runs the *whole* pytest suite against the standalone executable
|
|
4
|
-
# against the git_nested module in the checkout: conftest's GIT_NESTED_EXE
|
|
3
|
+
# Runs the *whole* pytest suite against the standalone Linux executable rather
|
|
4
|
+
# than against the git_nested module in the checkout: conftest's GIT_NESTED_EXE
|
|
5
5
|
# routes every cmd_git_nested() call through `git nested ...` on PATH, so the
|
|
6
6
|
# ~130 tests drive the frozen binary as a user would. That is what catches the
|
|
7
7
|
# failures unique to freezing -- a module PyInstaller did not collect, package
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
test:
|
|
31
31
|
runs-on: ubuntu-latest
|
|
32
32
|
steps:
|
|
33
|
-
- uses: actions/checkout@
|
|
33
|
+
- uses: actions/checkout@v7
|
|
34
34
|
with:
|
|
35
35
|
ref: ${{ inputs.ref }}
|
|
36
36
|
# no fetch-depth: 0 needed -- the version under test is read out of
|
|
@@ -56,19 +56,19 @@ jobs:
|
|
|
56
56
|
git config --global --add safe.directory "$PWD";
|
|
57
57
|
git config --global --add safe.directory "$PWD.git";
|
|
58
58
|
|
|
59
|
-
- uses: actions/download-artifact@
|
|
59
|
+
- uses: actions/download-artifact@v8
|
|
60
60
|
with:
|
|
61
61
|
name: ${{ inputs.artifact }}
|
|
62
62
|
|
|
63
63
|
# into its own directory: the env fixture puts the binary's directory on
|
|
64
64
|
# PATH, and the repo root is not a good thing to put there. Named
|
|
65
|
-
# git-nested-<version>-
|
|
65
|
+
# git-nested-<version>-<arch>-linux.tar.xz, so matched by glob. 'git-nested'
|
|
66
66
|
# inside is a symlink to the versioned binary, so both land in bin/
|
|
67
67
|
# together.
|
|
68
68
|
- name: Unpack the binary
|
|
69
69
|
run: |
|
|
70
70
|
mkdir -p bin
|
|
71
|
-
tar -C bin -xf git-nested-*-
|
|
71
|
+
tar -C bin -xf git-nested-*-linux.tar.xz
|
|
72
72
|
bin/git-nested --version
|
|
73
73
|
|
|
74
74
|
# no coverage flags: the code being executed lives in another process
|
|
@@ -4,26 +4,55 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
pull_request:
|
|
7
|
-
branches: ['
|
|
7
|
+
branches: ['**']
|
|
8
|
+
|
|
9
|
+
# One run per branch/PR at a time -- a new commit cancels whatever this
|
|
10
|
+
# workflow is still running for the same ref. Not on main: every push there
|
|
11
|
+
# is a merge, and each one should run to completion rather than being
|
|
12
|
+
# canceled by the next.
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
15
|
+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
8
16
|
|
|
9
17
|
jobs:
|
|
10
18
|
# Builds the standalone executable with the very script the release uses
|
|
11
19
|
# (scripts/create-python-exe.sh), so a change that breaks the freeze -- a new
|
|
12
|
-
# import, a moved data file -- fails here rather than on release day.
|
|
13
|
-
#
|
|
14
|
-
#
|
|
20
|
+
# import, a moved data file -- fails here rather than on release day. All
|
|
21
|
+
# three targets, because a break is usually a break on one of them only. The
|
|
22
|
+
# script smoke-tests each binary it produces; release-binary.yml additionally
|
|
23
|
+
# runs the Linux one across distros before attaching it to a release.
|
|
15
24
|
binary:
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
include:
|
|
29
|
+
# Intel macOS is deliberately absent: those runners are gone, and
|
|
30
|
+
# the arm64 binary is what a current Mac needs.
|
|
31
|
+
- target: linux
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
container: almalinux:8
|
|
34
|
+
- target: macos
|
|
35
|
+
runs-on: macos-latest
|
|
36
|
+
container: ''
|
|
37
|
+
- target: windows
|
|
38
|
+
runs-on: windows-latest
|
|
39
|
+
container: ''
|
|
16
40
|
uses: ./.github/workflows/build-binary.yml
|
|
17
41
|
with:
|
|
18
|
-
artifact: git-nested-binary-ci
|
|
42
|
+
artifact: git-nested-binary-ci-${{ matrix.target }}
|
|
43
|
+
runs-on: ${{ matrix.runs-on }}
|
|
44
|
+
container: ${{ matrix.container }}
|
|
19
45
|
|
|
20
|
-
# ... and then runs the full pytest suite through
|
|
21
|
-
# binary that builds but does not work still fails the PR.
|
|
46
|
+
# ... and then runs the full pytest suite through the Linux executable, so a
|
|
47
|
+
# binary that builds but does not work still fails the PR. Linux only: the
|
|
48
|
+
# suite is the same suite either way, and what it is checking here is that
|
|
49
|
+
# freezing did not drop anything -- a per-platform answer the smoke test in
|
|
50
|
+
# create-python-exe.sh already gives for the other two.
|
|
22
51
|
binary-test:
|
|
23
52
|
needs: binary
|
|
24
53
|
uses: ./.github/workflows/test-binary.yml
|
|
25
54
|
with:
|
|
26
|
-
artifact: git-nested-binary-ci
|
|
55
|
+
artifact: git-nested-binary-ci-linux
|
|
27
56
|
|
|
28
57
|
test:
|
|
29
58
|
runs-on: ${{ matrix.os }}
|
|
@@ -34,7 +63,7 @@ jobs:
|
|
|
34
63
|
|
|
35
64
|
steps:
|
|
36
65
|
- name: Checkout code
|
|
37
|
-
uses: actions/checkout@
|
|
66
|
+
uses: actions/checkout@v7
|
|
38
67
|
with:
|
|
39
68
|
# SonarCloud's blame-based analysis (new-code detection, issue
|
|
40
69
|
# authorship) needs real history, not the default shallow, tagless
|
|
@@ -58,7 +87,7 @@ jobs:
|
|
|
58
87
|
run: uv run poe all
|
|
59
88
|
- name: Upload coverage reports to Codecov
|
|
60
89
|
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'
|
|
61
|
-
uses: codecov/codecov-action@
|
|
90
|
+
uses: codecov/codecov-action@v7
|
|
62
91
|
with:
|
|
63
92
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
64
93
|
- name: Upload test results to Codecov
|
|
@@ -68,6 +97,6 @@ jobs:
|
|
|
68
97
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
69
98
|
- name: SonarCloud Scan
|
|
70
99
|
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'
|
|
71
|
-
uses: SonarSource/sonarqube-scan-action@
|
|
100
|
+
uses: SonarSource/sonarqube-scan-action@v8
|
|
72
101
|
env:
|
|
73
102
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
@@ -33,14 +33,15 @@ repos:
|
|
|
33
33
|
- id: debug-statements
|
|
34
34
|
- id: name-tests-test
|
|
35
35
|
args: ['--pytest-test-first']
|
|
36
|
+
# tests/unit/fakes.py holds the shared test doubles. It cannot be a
|
|
37
|
+
# conftest.py -- pytest registers every conftest under the bare name
|
|
38
|
+
# 'conftest', so a second one would collide in sys.modules with
|
|
39
|
+
# tests/conftest.py, which the whole e2e suite imports by that name.
|
|
40
|
+
exclude: ^tests/unit/fakes\.py$
|
|
36
41
|
|
|
37
42
|
# --- scripts --------------------------------------------------------
|
|
38
43
|
- id: check-executables-have-shebangs
|
|
39
44
|
- id: check-shebang-scripts-are-executable
|
|
40
|
-
# share/*.bash and share/*.sh are meant to be *sourced* into a user's
|
|
41
|
-
# shell (see share/enable-completion.sh), never executed directly --
|
|
42
|
-
# their shebang is there only so editors pick the right syntax mode.
|
|
43
|
-
exclude: ^share/
|
|
44
45
|
|
|
45
46
|
# --- repo integrity -------------------------------------------------
|
|
46
47
|
- id: detect-private-key
|
|
@@ -13,7 +13,13 @@ GIT_NESTED_ROOT=$(
|
|
|
13
13
|
) || return
|
|
14
14
|
|
|
15
15
|
export GIT_NESTED_ROOT
|
|
16
|
-
export PATH=$GIT_NESTED_ROOT/
|
|
16
|
+
export PATH=$GIT_NESTED_ROOT/bin:$PATH
|
|
17
17
|
export MANPATH=$GIT_NESTED_ROOT/man:$MANPATH
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Tab completion. The script is generated by git-nested itself, from the
|
|
20
|
+
# same tables the argument parser is built from, so it can never drift.
|
|
21
|
+
if [[ -n ${ZSH_VERSION-} ]]; then
|
|
22
|
+
eval "$(git-nested completion zsh)"
|
|
23
|
+
else
|
|
24
|
+
eval "$(git-nested completion bash)"
|
|
25
|
+
fi
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Contributing to git-nested
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/YOUR_USERNAME/git-nested
|
|
7
|
+
cd git-nested
|
|
8
|
+
uv sync --dev # or: pip install -e ".[dev]"
|
|
9
|
+
uv run poe all # verify your setup: the full gate
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Making changes
|
|
13
|
+
|
|
14
|
+
1. Branch off `main`.
|
|
15
|
+
2. Make your change and add tests for it.
|
|
16
|
+
3. Run `uv run poe all` before opening a PR.
|
|
17
|
+
4. Open a PR against `main` and fill out the template.
|
|
18
|
+
|
|
19
|
+
## How the tests are laid out
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
tests/
|
|
23
|
+
conftest.py shared fixtures and the git-nested runner
|
|
24
|
+
unit/ fast, no repositories -- one function at a time, with fakes
|
|
25
|
+
e2e/ real git repositories in a temp dir, driven through the CLI
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Each directory is also a marker (`-m unit`, `-m e2e`), applied by path, so a
|
|
29
|
+
new test file needs nothing added to it.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv run poe test # everything, with the coverage gate
|
|
33
|
+
uv run poe test-unit # just the unit tests -- seconds, no coverage gate
|
|
34
|
+
uv run poe test tests/e2e/test_clone.py # one file
|
|
35
|
+
uv run pytest -k clone # or pytest directly
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`poe test` requires **100% line coverage** and fails below it. A line that
|
|
39
|
+
genuinely cannot be reached gets a `# pragma: no cover` with a comment saying
|
|
40
|
+
why.
|
|
41
|
+
|
|
42
|
+
Tests run in parallel against real repositories, so a flake is possible. To
|
|
43
|
+
hunt one down:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run poe flakefinder # reruns the suite 200 times; add -k to narrow it
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### The standalone executable
|
|
50
|
+
|
|
51
|
+
Releases ship a single-file executable built with PyInstaller
|
|
52
|
+
(`scripts/create-python-exe.sh`). Freezing can break things the module tests
|
|
53
|
+
never see, so the whole suite can also run *through* the binary:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv run poe test-exe # build dist/git-nested and test it
|
|
57
|
+
uv run poe exe # just build it
|
|
58
|
+
GIT_NESTED_EXE=dist/git-nested uv run pytest # test an existing binary
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
CI does both: `test.yml` builds the binary and tests it on every PR;
|
|
62
|
+
`release-binary.yml` repeats that across six distro images before attaching
|
|
63
|
+
the asset to a release.
|
|
64
|
+
|
|
65
|
+
## Code quality
|
|
66
|
+
|
|
67
|
+
`uv run poe all` runs the full gate CI enforces. Each check has its own task:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run poe pre-commit # repo hygiene: whitespace, YAML/TOML/JSON, shebangs
|
|
71
|
+
uv run poe lint # ruff lint
|
|
72
|
+
uv run poe format # ruff format
|
|
73
|
+
uv run poe mypy # static typing
|
|
74
|
+
uv run poe pyright # static typing (different checker, both must pass)
|
|
75
|
+
uv run poe bandit # security linting
|
|
76
|
+
uv run poe pip-audit # dependency vulnerability scan
|
|
77
|
+
uv run poe complexity # cognitive complexity (complexipy)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Commit messages
|
|
81
|
+
|
|
82
|
+
Imperative mood ("Add", not "Added"), first line under 72 characters,
|
|
83
|
+
reference issues where relevant.
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
Update [README.md](README.md) for user-facing changes and
|
|
88
|
+
[docs/diagrams.md](docs/diagrams.md) when a command's underlying git calls
|
|
89
|
+
change.
|
|
90
|
+
|
|
91
|
+
## Reporting bugs and security issues
|
|
92
|
+
|
|
93
|
+
Open a GitHub issue with steps to reproduce, expected vs. actual behaviour,
|
|
94
|
+
and your git-nested/git/Python versions. For security vulnerabilities, see
|
|
95
|
+
[SECURITY.md](SECURITY.md) instead of opening a public issue.
|