git-nested 1.0.4__tar.gz → 1.1.0__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.
Files changed (64) hide show
  1. git_nested-1.1.0/.github/CODEOWNERS +10 -0
  2. git_nested-1.1.0/.github/workflows/build-binary.yml +78 -0
  3. git_nested-1.1.0/.github/workflows/release-binary.yml +166 -0
  4. git_nested-1.1.0/.github/workflows/test-binary.yml +81 -0
  5. {git_nested-1.0.4 → git_nested-1.1.0}/.github/workflows/test.yml +28 -0
  6. {git_nested-1.0.4 → git_nested-1.1.0}/.gitignore +3 -0
  7. git_nested-1.1.0/.pre-commit-config.yaml +62 -0
  8. {git_nested-1.0.4 → git_nested-1.1.0}/.rc +0 -0
  9. {git_nested-1.0.4 → git_nested-1.1.0}/CONTRIBUTING.md +46 -4
  10. {git_nested-1.0.4 → git_nested-1.1.0}/PKG-INFO +44 -3
  11. {git_nested-1.0.4 → git_nested-1.1.0}/README.md +43 -2
  12. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git_nested.egg-info/PKG-INFO +44 -3
  13. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git_nested.egg-info/SOURCES.txt +14 -1
  14. git_nested-1.1.0/lib/git_nested.py +2690 -0
  15. {git_nested-1.0.4 → git_nested-1.1.0}/pyproject.toml +63 -1
  16. git_nested-1.1.0/scripts/create-python-exe.sh +189 -0
  17. {git_nested-1.0.4 → git_nested-1.1.0}/share/completion.bash +2 -2
  18. {git_nested-1.0.4 → git_nested-1.1.0}/share/git-nested.fish +2 -1
  19. {git_nested-1.0.4 → git_nested-1.1.0}/share/zsh-completion/_git-nested +2 -2
  20. git_nested-1.1.0/sonar-project.properties +9 -0
  21. {git_nested-1.0.4 → git_nested-1.1.0}/test/conftest.py +203 -129
  22. {git_nested-1.0.4 → git_nested-1.1.0}/test/scenarios/test_filemode.py +6 -7
  23. {git_nested-1.0.4 → git_nested-1.1.0}/test/scenarios/test_filter.py +0 -1
  24. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_branch.py +11 -2
  25. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_clone.py +3 -3
  26. git_nested-1.1.0/test/test_commit.py +50 -0
  27. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_completion.py +1 -0
  28. git_nested-1.1.0/test/test_diff.py +158 -0
  29. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_error.py +5 -2
  30. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_gitignore.py +1 -1
  31. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_init.py +30 -2
  32. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_nested_in_nested.py +1 -2
  33. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_pull.py +25 -16
  34. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_push.py +61 -6
  35. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_reclone.py +3 -3
  36. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_status.py +3 -3
  37. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_version.py +2 -3
  38. git_nested-1.1.0/test/unittest/conftest.py +62 -0
  39. git_nested-1.1.0/test/unittest/test_command.py +112 -0
  40. git_nested-1.1.0/test/unittest/test_helpers.py +174 -0
  41. {git_nested-1.0.4 → git_nested-1.1.0}/test/unittest/test_logging.py +9 -12
  42. git_nested-1.1.0/test/unittest/test_repo_business.py +492 -0
  43. git_nested-1.1.0/uv.lock +2237 -0
  44. git_nested-1.0.4/lib/git_nested.py +0 -2067
  45. git_nested-1.0.4/uv.lock +0 -689
  46. {git_nested-1.0.4 → git_nested-1.1.0}/.codecov.yml +0 -0
  47. {git_nested-1.0.4 → git_nested-1.1.0}/.fish.rc +0 -0
  48. {git_nested-1.0.4 → git_nested-1.1.0}/.gitattributes +0 -0
  49. {git_nested-1.0.4 → git_nested-1.1.0}/.github/workflows/publish.yml +0 -0
  50. {git_nested-1.0.4 → git_nested-1.1.0}/LICENSE +0 -0
  51. {git_nested-1.0.4 → git_nested-1.1.0}/docs/diagrams.md +0 -0
  52. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git-nested +0 -0
  53. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git_nested.egg-info/dependency_links.txt +0 -0
  54. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git_nested.egg-info/entry_points.txt +0 -0
  55. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git_nested.egg-info/requires.txt +0 -0
  56. {git_nested-1.0.4 → git_nested-1.1.0}/lib/git_nested.egg-info/top_level.txt +0 -0
  57. {git_nested-1.0.4 → git_nested-1.1.0}/setup.cfg +0 -0
  58. {git_nested-1.0.4 → git_nested-1.1.0}/share/enable-completion.sh +0 -0
  59. {git_nested-1.0.4 → git_nested-1.1.0}/test/scenarios/test_extraction.py +1 -1
  60. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_clean.py +0 -0
  61. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_encode.py +0 -0
  62. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_fetch.py +0 -0
  63. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_rebase.py +2 -2
  64. {git_nested-1.0.4 → git_nested-1.1.0}/test/test_submodule.py +0 -0
@@ -0,0 +1,10 @@
1
+ # Code owners for git-nested
2
+ #
3
+ # Owners listed here are automatically requested for review on pull requests
4
+ # that touch matching files. The last matching pattern wins.
5
+ #
6
+ # Note: GitHub never requests a review from the author of the pull request,
7
+ # so PRs opened by an owner get no automatic reviewer.
8
+
9
+ # Default owner for everything in the repository
10
+ * @thorsten-klein
@@ -0,0 +1,78 @@
1
+ name: Build binary
2
+
3
+ # Builds the standalone 'git-nested' executable and uploads it as an artifact.
4
+ # Called by test.yml (so every push/PR proves the recipe still builds) and by
5
+ # release-binary.yml (which attaches it to the release) -- so what CI exercises
6
+ # is built by exactly the recipe that ships, rather than a lookalike that can
7
+ # drift away from it.
8
+ on:
9
+ workflow_call:
10
+ inputs:
11
+ ref:
12
+ description: 'Commit/tag/branch to build. Empty: whatever triggered the caller.'
13
+ type: string
14
+ required: false
15
+ default: ''
16
+ artifact:
17
+ description: 'Name of the uploaded artifact'
18
+ type: string
19
+ required: false
20
+ default: git-nested-binary
21
+ outputs:
22
+ version:
23
+ description: 'Version of the built git-nested, e.g. 1.2.3 -- for composing the exact git-nested-<version>-x64-linux.tar.xz filename in a caller'
24
+ value: ${{ jobs.build.outputs.version }}
25
+
26
+ jobs:
27
+ build:
28
+ runs-on: ubuntu-latest
29
+ # A PyInstaller binary links the glibc of the machine that built it, and
30
+ # glibc is only backward compatible -- built on ubuntu-latest it would
31
+ # refuse to start on anything older than that runner. AlmaLinux 8 has
32
+ # glibc 2.28, the oldest that still runs the node20 the actions below
33
+ # need, which covers every distro from Ubuntu 20.04 / Debian 10 /
34
+ # Fedora 29 / RHEL 8 onwards. glibc-based distros only -- musl ones
35
+ # (Alpine) can never run this binary.
36
+ container: almalinux:8
37
+ outputs:
38
+ version: ${{ steps.build.outputs.version }}
39
+ steps:
40
+ # Before the checkout -- without git, actions/checkout silently falls
41
+ # back to downloading a tarball, and a checkout with no .git has no
42
+ # tags for setuptools-scm to derive the version from.
43
+ - name: Install build prerequisites
44
+ run: dnf -y install python3.12 python3.12-pip git tar xz
45
+
46
+ - uses: actions/checkout@v4
47
+ with:
48
+ ref: ${{ inputs.ref }}
49
+ # the version frozen into the binary comes from the tags, and the
50
+ # default shallow checkout fetches none of them.
51
+ fetch-depth: 0
52
+
53
+ # the checkout is owned by a different user than this container's root,
54
+ # which git refuses to read from until told it is not a security issue.
55
+ - name: Trust the checkout
56
+ run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
57
+
58
+ - name: Build the executable
59
+ id: build
60
+ run: |
61
+ scripts/create-python-exe.sh --output dist --python python3.12
62
+ # 'git-nested Version: X.Y.Z' -> X.Y.Z. Exposed as a job output so a
63
+ # caller can compose the exact git-nested-<version>-x64-linux.tar.xz
64
+ # filename itself (e.g. release-binary.yml's 'gh release upload')
65
+ # instead of globbing for it.
66
+ echo "version=$(dist/git-nested --version | head -1 | cut -d' ' -f3)" >> "$GITHUB_OUTPUT"
67
+
68
+ # the .tar.xz rather than the bare executable. It is what a release
69
+ # actually ships, and unlike a zipped artifact it keeps the exec bit,
70
+ # so consumers just untar and run it. Named git-nested-<version>-x64-linux
71
+ # by create-python-exe.sh, so the version can't be spelled out here --
72
+ # matched by glob instead (see steps.build.outputs.version for callers
73
+ # that need the exact name).
74
+ - uses: actions/upload-artifact@v4
75
+ with:
76
+ name: ${{ inputs.artifact }}
77
+ path: dist/git-nested-*-x64-linux.tar.xz
78
+ if-no-files-found: error
@@ -0,0 +1,166 @@
1
+ name: Release binary
2
+
3
+ # Builds the single-file 'git-nested' executable (see
4
+ # scripts/create-python-exe.sh) and attaches
5
+ # git-nested-<version>-x64-linux.tar.xz to the release, so git-nested can be
6
+ # used on a machine that has no python -- and no pip, no uv -- at all.
7
+ #
8
+ # Separate from publish.yml on purpose. That one is triggered by the tag push
9
+ # and uploads to PyPI, while attaching an asset needs the GitHub *release* to
10
+ # already exist, which is only guaranteed once it is published.
11
+ on:
12
+ release:
13
+ types: [published]
14
+ # on-demand -- builds and verifies without touching any release. Give 'tag'
15
+ # to also (re-)attach the asset to an existing release, e.g. after fixing
16
+ # the build for one already published.
17
+ workflow_dispatch:
18
+ inputs:
19
+ tag:
20
+ description: 'Existing release tag to build from and attach the asset to (empty: build + verify only)'
21
+ required: false
22
+ default: ''
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ concurrency:
28
+ group: ${{ github.workflow }}-${{ github.event.release.tag_name || inputs.tag || github.ref }}
29
+ cancel-in-progress: true
30
+
31
+ env:
32
+ # The release this builds. Its tag is what setuptools-scm turns into the
33
+ # version frozen into the binary, so checking out anything else would
34
+ # produce an executable that misreports itself. Empty on a plain
35
+ # workflow_dispatch -- then the triggering ref is built as-is.
36
+ REF: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
37
+
38
+ jobs:
39
+ # the glibc floor and every other build decision live there, shared with
40
+ # test.yml so both exercise the same executable. 'env.REF' is not available
41
+ # to a reusable workflow's inputs, so the ref is spelled out again.
42
+ build:
43
+ uses: ./.github/workflows/build-binary.yml
44
+ with:
45
+ ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
46
+ artifact: git-nested-binary
47
+
48
+ # The whole pytest suite, driven through the binary that is about to be
49
+ # attached -- the check that it is functionally complete, not just startable.
50
+ test:
51
+ needs: build
52
+ uses: ./.github/workflows/test-binary.yml
53
+ with:
54
+ ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
55
+ artifact: git-nested-binary
56
+
57
+ # Proves the "runs on any x86_64 Linux" claim rather than assuming it. The
58
+ # binary is dropped into stock distro images that have no python installed,
59
+ # and has to drive a real nested repo end to end there.
60
+ verify:
61
+ needs: build
62
+ runs-on: ubuntu-latest
63
+ strategy:
64
+ fail-fast: false
65
+ matrix:
66
+ include:
67
+ # the glibc floor it was built against, and the oldest still-
68
+ # supported LTS releases -- the ones a modern-runner build breaks
69
+ - image: almalinux:8
70
+ install: dnf -y install git
71
+ - image: ubuntu:22.04
72
+ install: apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git
73
+ - image: debian:12
74
+ install: apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git
75
+ # ... plus the current end of the range, where a binary built too
76
+ # old would fail instead
77
+ - image: ubuntu:24.04
78
+ install: apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git
79
+ - image: fedora:latest
80
+ install: dnf -y install git
81
+ - image: archlinux:latest
82
+ install: pacman -Sy --noconfirm git
83
+ steps:
84
+ - uses: actions/download-artifact@v4
85
+ with:
86
+ name: git-nested-binary
87
+
88
+ # Named git-nested-<version>-x64-linux.tar.xz -- resolved by glob since
89
+ # the version isn't known here.
90
+ - name: Unpack
91
+ run: tar -xf git-nested-*-x64-linux.tar.xz
92
+
93
+ # git is the one thing that does have to be installed -- git-nested
94
+ # drives it and refuses to start without it. Python deliberately is not:
95
+ # if any of these images had to install it, the binary would not be
96
+ # doing its job.
97
+ - name: Write the verification script
98
+ run: |
99
+ cat > verify.sh <<'EOF'
100
+ set -eu
101
+ if command -v python3 >/dev/null; then
102
+ echo "note: this image happens to ship python3; the binary does not use it"
103
+ else
104
+ echo "note: no python3 in this image at all"
105
+ fi
106
+
107
+ git-nested --version
108
+
109
+ # invoked as `git nested`, the way users run it: git finds the
110
+ # subcommand by looking for a 'git-nested' on PATH.
111
+ git config --global user.email ci@example.com
112
+ git config --global user.name CI
113
+ git config --global init.defaultBranch master
114
+ git config --global --add safe.directory '*'
115
+
116
+ mkdir -p /tmp/repo/doc
117
+ cd /tmp/repo
118
+ git init -q
119
+ touch doc/README.md
120
+ git add -A
121
+ git commit -qm "initial"
122
+
123
+ git nested init doc
124
+ test -f doc/.gitnested
125
+ cat doc/.gitnested
126
+ git nested status
127
+ EOF
128
+
129
+ # The binary and the script are mounted in, so the distro images need
130
+ # neither tar/xz nor anything else to take part beyond git -- the point
131
+ # is that nothing else is required. 'git-nested' is now a symlink to the
132
+ # versioned binary, so both have to be mounted together (into PATH)
133
+ # rather than just the single file -- a lone symlink mount would
134
+ # dangle, pointing at a sibling that was never brought along.
135
+ - name: Run on ${{ matrix.image }}
136
+ run: |
137
+ docker run --rm \
138
+ -v "$PWD:/work" -w /work \
139
+ ${{ matrix.image }} \
140
+ sh -c '${{ matrix.install }} >/dev/null && export PATH="/work:$PATH" && sh verify.sh'
141
+
142
+ upload:
143
+ # 'build' listed directly (not just transitively via 'test'/'verify')
144
+ # because a job's outputs are only visible to jobs that name it in
145
+ # 'needs'.
146
+ needs: [build, test, verify]
147
+ # nothing to attach when workflow_dispatch ran without a tag
148
+ if: github.event_name == 'release' || inputs.tag != ''
149
+ runs-on: ubuntu-latest
150
+ permissions:
151
+ contents: write
152
+ steps:
153
+ - uses: actions/download-artifact@v4
154
+ with:
155
+ name: git-nested-binary
156
+
157
+ # --clobber so re-running against an already-attached release replaces
158
+ # the asset instead of failing halfway. The filename is composed from
159
+ # 'build's version output -- this is the point that actually names the
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 }}
162
+ env:
163
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
164
+ GH_REPO: ${{ github.repository }}
165
+ ARCHIVE: git-nested-${{ needs.build.outputs.version }}-x64-linux.tar.xz
166
+ run: gh release upload "$REF" "$ARCHIVE" --clobber
@@ -0,0 +1,81 @@
1
+ name: Test binary
2
+
3
+ # Runs the *whole* pytest suite against the standalone executable instead of
4
+ # against the git_nested module in the checkout: conftest's GIT_NESTED_EXE
5
+ # routes every cmd_git_nested() call through `git nested ...` on PATH, so the
6
+ # ~130 tests drive the frozen binary as a user would. That is what catches the
7
+ # failures unique to freezing -- a module PyInstaller did not collect, package
8
+ # metadata missing so the version is wrong, a data file left out of the archive
9
+ # -- none of which the module tests can see.
10
+ #
11
+ # Called by test.yml (every push/PR) and by release-binary.yml (before the
12
+ # asset is attached). release-binary.yml additionally runs the binary across
13
+ # distro images, which is the other half: this job proves the binary is
14
+ # complete, that one proves it is portable.
15
+ on:
16
+ workflow_call:
17
+ inputs:
18
+ ref:
19
+ description: 'Commit/tag/branch whose tests to run. Empty: whatever triggered the caller.'
20
+ type: string
21
+ required: false
22
+ default: ''
23
+ artifact:
24
+ description: 'Name of the artifact holding the built binary'
25
+ type: string
26
+ required: false
27
+ default: git-nested-binary
28
+
29
+ jobs:
30
+ test:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ ref: ${{ inputs.ref }}
36
+ # no fetch-depth: 0 needed -- the version under test is read out of
37
+ # the binary (conftest's _version_under_test), not derived from the
38
+ # tags of this checkout.
39
+
40
+ - name: Install uv
41
+ uses: astral-sh/setup-uv@v7
42
+ with:
43
+ python-version: '3.13'
44
+ enable-cache: true
45
+
46
+ # for pytest, pytest-xdist and the git_nested import conftest still needs
47
+ # for its helpers -- the code under test comes from the binary.
48
+ - name: Setup project
49
+ run: uv sync --frozen
50
+
51
+ - name: git config
52
+ run: |
53
+ git config --global user.email "you@example.com";
54
+ git config --global user.name "Your Name";
55
+ git config --global init.defaultBranch "master";
56
+ git config --global --add safe.directory "$PWD";
57
+ git config --global --add safe.directory "$PWD.git";
58
+
59
+ - uses: actions/download-artifact@v4
60
+ with:
61
+ name: ${{ inputs.artifact }}
62
+
63
+ # into its own directory: the env fixture puts the binary's directory on
64
+ # PATH, and the repo root is not a good thing to put there. Named
65
+ # git-nested-<version>-x64-linux.tar.xz, so matched by glob. 'git-nested'
66
+ # inside is a symlink to the versioned binary, so both land in bin/
67
+ # together.
68
+ - name: Unpack the binary
69
+ run: |
70
+ mkdir -p bin
71
+ tar -C bin -xf git-nested-*-x64-linux.tar.xz
72
+ bin/git-nested --version
73
+
74
+ # no coverage flags: the code being executed lives in another process
75
+ # inside the frozen archive, so there is nothing here for coverage to
76
+ # measure. test.yml's module run is what the coverage numbers come from.
77
+ - name: Run the suite against the binary
78
+ env:
79
+ GIT_NESTED_EXE: ${{ github.workspace }}/bin/git-nested
80
+ PYTHONIOENCODING: utf-8
81
+ run: uv run pytest -v -n=auto -W error
@@ -7,6 +7,24 @@ on:
7
7
  branches: ['*']
8
8
 
9
9
  jobs:
10
+ # Builds the standalone executable with the very script the release uses
11
+ # (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. The
13
+ # script smoke-tests the binary it produces; release-binary.yml additionally
14
+ # runs it across distros before attaching it to a release.
15
+ binary:
16
+ uses: ./.github/workflows/build-binary.yml
17
+ with:
18
+ artifact: git-nested-binary-ci
19
+
20
+ # ... and then runs the full pytest suite through that executable, so a
21
+ # binary that builds but does not work still fails the PR.
22
+ binary-test:
23
+ needs: binary
24
+ uses: ./.github/workflows/test-binary.yml
25
+ with:
26
+ artifact: git-nested-binary-ci
27
+
10
28
  test:
11
29
  runs-on: ${{ matrix.os }}
12
30
  strategy:
@@ -17,6 +35,11 @@ jobs:
17
35
  steps:
18
36
  - name: Checkout code
19
37
  uses: actions/checkout@v4
38
+ with:
39
+ # SonarCloud's blame-based analysis (new-code detection, issue
40
+ # authorship) needs real history, not the default shallow, tagless
41
+ # checkout.
42
+ fetch-depth: 0
20
43
  - name: Install uv
21
44
  uses: astral-sh/setup-uv@v7
22
45
  with:
@@ -43,3 +66,8 @@ jobs:
43
66
  uses: codecov/test-results-action@v1
44
67
  with:
45
68
  token: ${{ secrets.CODECOV_TOKEN }}
69
+ - name: SonarCloud Scan
70
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'
71
+ uses: SonarSource/sonarqube-scan-action@v4
72
+ env:
73
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -9,5 +9,8 @@ build/
9
9
  .pytest_cache/
10
10
  .coverage
11
11
  htmlcov/
12
+ coverage.xml
13
+ .coverage*
14
+ junit.xml
12
15
  *.whl
13
16
  *.tar.gz
@@ -0,0 +1,62 @@
1
+ # Repo-wide hygiene gate. Run over the whole tree by `uv run poe pre-commit`,
2
+ # the first step of `uv run poe all`. Nothing installs an actual git hook --
3
+ # that stays a per-developer `pre-commit install` if you want it.
4
+ #
5
+ # Everything language-specific already has its own poe task backed by a version
6
+ # pinned in pyproject.toml's `lint` dependency group -- ruff, mypy, pyright,
7
+ # bandit, pip-audit, complexipy. Duplicating any of them here would mean a
8
+ # second, independently-pinned copy drifting from the one CI runs, so this file
9
+ # deliberately stays limited to the checks nothing else covers.
10
+ repos:
11
+ # General pre-commit hooks
12
+ - repo: https://github.com/pre-commit/pre-commit-hooks
13
+ rev: v6.0.0
14
+ hooks:
15
+ # --- whitespace -----------------------------------------------------
16
+ # .patch/.diff are excluded throughout: trailing whitespace inside a
17
+ # hunk is content, and git's "-- \n" signature separator is load-bearing.
18
+ # Rewriting either corrupts the patch.
19
+ - id: trailing-whitespace
20
+ exclude: ^.*\.(patch|diff)$
21
+ - id: end-of-file-fixer
22
+ exclude: ^.*\.(patch|diff)$
23
+ - id: mixed-line-ending
24
+ args: ['--fix=lf']
25
+ exclude: ^.*\.(patch|diff)$
26
+
27
+ # --- parseability of the files that configure everything else -------
28
+ - id: check-yaml
29
+ - id: check-toml
30
+ - id: check-json
31
+
32
+ # --- python ---------------------------------------------------------
33
+ - id: debug-statements
34
+ - id: name-tests-test
35
+ args: ['--pytest-test-first']
36
+
37
+ # --- scripts --------------------------------------------------------
38
+ - id: check-executables-have-shebangs
39
+ - 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
+ # --- repo integrity -------------------------------------------------
46
+ - id: detect-private-key
47
+ - id: check-added-large-files
48
+ args: ['--maxkb=500', '--enforce-all']
49
+ - id: check-case-conflict
50
+ - id: check-merge-conflict
51
+ - id: check-symlinks
52
+ - id: destroyed-symlinks
53
+ - id: forbid-submodules
54
+
55
+
56
+ # Configuration
57
+ default_language_version:
58
+ python: python3.12
59
+
60
+ # Minimum pre-commit version -- matches the floor pyproject.toml's `lint`
61
+ # dependency group pins.
62
+ minimum_pre_commit_version: '4.0.0'
File without changes
@@ -65,16 +65,58 @@ uv run poe test -vv
65
65
  uv run pytest
66
66
  ```
67
67
 
68
+ ### Testing the Standalone Executable
69
+
70
+ Releases ship a single-file executable built with PyInstaller
71
+ (`scripts/create-python-exe.sh`). Freezing can break things the module tests
72
+ never see — an import PyInstaller did not collect, missing package metadata —
73
+ so the whole suite can be run *through* the binary instead:
74
+
75
+ ```bash
76
+ # Build dist/git-nested and run every test against it
77
+ uv run poe test-exe
78
+
79
+ # Just build it
80
+ uv run poe exe
81
+
82
+ # Or point the tests at any executable you already have
83
+ GIT_NESTED_EXE=dist/git-nested uv run pytest
84
+ ```
85
+
86
+ With `GIT_NESTED_EXE` set, `cmd_git_nested()` in `test/conftest.py` runs
87
+ `git nested ...` as a subprocess against that binary rather than calling the
88
+ module in-process; without it, nothing changes. CI does both: `test.yml` builds
89
+ the binary and runs the suite against it on every PR, and `release-binary.yml`
90
+ repeats that plus a run across six distro images before attaching the asset to
91
+ a release.
92
+
68
93
  ### Code Quality
69
94
 
70
- We use [Ruff](https://github.com/astral-sh/ruff) for both linting and formatting:
95
+ `uv run poe all` runs the full gate CI enforces: repo hygiene, linting,
96
+ formatting, static typing, security, dependency, and complexity checks, then
97
+ the test suite. Each has its own `poe` task if you want to run just one:
71
98
 
72
99
  ```bash
73
- # Check code quality
74
- uv run poe lint
100
+ # Repo-wide hygiene (whitespace, YAML/TOML/JSON parseability, shebangs, ...)
101
+ uv run poe pre-commit
75
102
 
76
- # Auto-format code
103
+ # Lint and auto-format with Ruff (https://github.com/astral-sh/ruff)
104
+ uv run poe lint
77
105
  uv run poe format
106
+
107
+ # Static typing -- mypy and pyright are both run since they catch
108
+ # different things; passing both is required
109
+ uv run poe mypy
110
+ uv run poe pyright
111
+
112
+ # Security linting (bandit) and dependency vulnerability scanning (pip-audit)
113
+ uv run poe bandit
114
+ uv run poe pip-audit
115
+
116
+ # Cognitive complexity (complexipy); threshold is set in pyproject.toml's
117
+ # [tool.complexipy] to catch complexity creep in new code, not to demand
118
+ # every existing function be simple
119
+ uv run poe complexity
78
120
  ```
79
121
 
80
122
  Make sure all checks pass before submitting your PR.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-nested
3
- Version: 1.0.4
3
+ Version: 1.1.0
4
4
  Summary: Git Submodule Alternative
5
5
  Author-email: Thorsten Klein <thorsten.klein.git@gmail.com>
6
6
  License: MIT
@@ -80,7 +80,32 @@ git-nested now supports nested repositories within nested repositories (recursiv
80
80
  pip install git+https://github.com/thorsten-klein/git-nested
81
81
  ```
82
82
 
83
- #### Method 2: From Source with Shell Integration
83
+ #### Method 2: Standalone Executable (no Python needed)
84
+
85
+ Every [release](https://github.com/thorsten-klein/git-nested/releases) ships
86
+ `git-nested-<version>-x64-linux.tar.xz`: a single file with git-nested, its
87
+ dependencies and a Python interpreter inside. Only git itself has to be
88
+ installed.
89
+
90
+ ```bash
91
+ # Find the asset from the latest release
92
+ ASSET=$(curl -sSL https://api.github.com/repos/thorsten-klein/git-nested/releases/latest | grep -o 'https://[^"]*\.tar\.xz')
93
+
94
+ # Download the asset and unpack it
95
+ curl -sSL "$ASSET" | tar -xJ
96
+ sudo install git-nested /usr/local/bin/ # any directory on PATH works
97
+ ```
98
+
99
+ The file has to keep the name `git-nested` — that is how `git nested ...`
100
+ finds it. The archive also contains a versioned copy of the binary (e.g.
101
+ `git-nested-1.2.3`) that `git-nested` symlinks to, and the completion scripts
102
+ from `share/`.
103
+
104
+ Built on x86_64 with glibc 2.28, so it runs on any x86_64 Linux from that
105
+ version onwards (Ubuntu 20.04, Debian 10, RHEL/AlmaLinux 8 and newer). Not on
106
+ musl distros such as Alpine.
107
+
108
+ #### Method 3: From Source with Shell Integration
84
109
 
85
110
  Adds git-nested to PATH and enables tab completion:
86
111
 
@@ -90,7 +115,7 @@ echo 'source /path/to/git-nested/.rc' >> ~/.bashrc
90
115
  source ~/.bashrc
91
116
  ```
92
117
 
93
- #### Method 3: From Source (Manual)
118
+ #### Method 4: From Source (Manual)
94
119
 
95
120
  ```bash
96
121
  git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
@@ -195,6 +220,22 @@ Fetch remote content for a nested repository.
195
220
  git nested fetch --help
196
221
  ```
197
222
 
223
+ #### `git nested diff`
224
+
225
+ Show the local diff of a nested repo compared to upstream. Fetches the latest upstream
226
+ content first, so it always compares against the freshest available state. Requires that
227
+ there are no uncommitted changes in the outer repository.
228
+
229
+ ```bash
230
+ git nested diff --help
231
+ ```
232
+
233
+ **Example:**
234
+ ```bash
235
+ git nested diff ext/lib
236
+ git nested diff --all # Diff all nested repos
237
+ ```
238
+
198
239
  #### `git nested branch`
199
240
 
200
241
  Create a branch with local nested commits for manual conflict resolution.
@@ -54,7 +54,32 @@ git-nested now supports nested repositories within nested repositories (recursiv
54
54
  pip install git+https://github.com/thorsten-klein/git-nested
55
55
  ```
56
56
 
57
- #### Method 2: From Source with Shell Integration
57
+ #### Method 2: Standalone Executable (no Python needed)
58
+
59
+ Every [release](https://github.com/thorsten-klein/git-nested/releases) ships
60
+ `git-nested-<version>-x64-linux.tar.xz`: a single file with git-nested, its
61
+ dependencies and a Python interpreter inside. Only git itself has to be
62
+ installed.
63
+
64
+ ```bash
65
+ # Find the asset from the latest release
66
+ ASSET=$(curl -sSL https://api.github.com/repos/thorsten-klein/git-nested/releases/latest | grep -o 'https://[^"]*\.tar\.xz')
67
+
68
+ # Download the asset and unpack it
69
+ curl -sSL "$ASSET" | tar -xJ
70
+ sudo install git-nested /usr/local/bin/ # any directory on PATH works
71
+ ```
72
+
73
+ The file has to keep the name `git-nested` — that is how `git nested ...`
74
+ finds it. The archive also contains a versioned copy of the binary (e.g.
75
+ `git-nested-1.2.3`) that `git-nested` symlinks to, and the completion scripts
76
+ from `share/`.
77
+
78
+ Built on x86_64 with glibc 2.28, so it runs on any x86_64 Linux from that
79
+ version onwards (Ubuntu 20.04, Debian 10, RHEL/AlmaLinux 8 and newer). Not on
80
+ musl distros such as Alpine.
81
+
82
+ #### Method 3: From Source with Shell Integration
58
83
 
59
84
  Adds git-nested to PATH and enables tab completion:
60
85
 
@@ -64,7 +89,7 @@ echo 'source /path/to/git-nested/.rc' >> ~/.bashrc
64
89
  source ~/.bashrc
65
90
  ```
66
91
 
67
- #### Method 3: From Source (Manual)
92
+ #### Method 4: From Source (Manual)
68
93
 
69
94
  ```bash
70
95
  git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
@@ -169,6 +194,22 @@ Fetch remote content for a nested repository.
169
194
  git nested fetch --help
170
195
  ```
171
196
 
197
+ #### `git nested diff`
198
+
199
+ Show the local diff of a nested repo compared to upstream. Fetches the latest upstream
200
+ content first, so it always compares against the freshest available state. Requires that
201
+ there are no uncommitted changes in the outer repository.
202
+
203
+ ```bash
204
+ git nested diff --help
205
+ ```
206
+
207
+ **Example:**
208
+ ```bash
209
+ git nested diff ext/lib
210
+ git nested diff --all # Diff all nested repos
211
+ ```
212
+
172
213
  #### `git nested branch`
173
214
 
174
215
  Create a branch with local nested commits for manual conflict resolution.