git-nested 1.0.4__tar.gz → 1.0.5__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.0.5/.github/CODEOWNERS +10 -0
- git_nested-1.0.5/.github/workflows/build-binary.yml +62 -0
- git_nested-1.0.5/.github/workflows/release-binary.yml +157 -0
- git_nested-1.0.5/.github/workflows/test-binary.yml +78 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/.github/workflows/test.yml +18 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/.gitignore +2 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/CONTRIBUTING.md +25 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/PKG-INFO +37 -3
- {git_nested-1.0.4 → git_nested-1.0.5}/README.md +36 -2
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.egg-info/PKG-INFO +37 -3
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.egg-info/SOURCES.txt +6 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.py +43 -2
- {git_nested-1.0.4 → git_nested-1.0.5}/pyproject.toml +6 -0
- git_nested-1.0.5/scripts/create-python-exe.sh +176 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/share/completion.bash +2 -2
- {git_nested-1.0.4 → git_nested-1.0.5}/share/git-nested.fish +2 -1
- {git_nested-1.0.4 → git_nested-1.0.5}/share/zsh-completion/_git-nested +2 -2
- {git_nested-1.0.4 → git_nested-1.0.5}/test/conftest.py +76 -10
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_clone.py +2 -3
- git_nested-1.0.5/test/test_diff.py +158 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_error.py +5 -2
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_pull.py +4 -4
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_version.py +2 -3
- {git_nested-1.0.4 → git_nested-1.0.5}/.codecov.yml +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/.fish.rc +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/.gitattributes +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/.github/workflows/publish.yml +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/.rc +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/LICENSE +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/docs/diagrams.md +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git-nested +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.egg-info/dependency_links.txt +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.egg-info/entry_points.txt +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.egg-info/requires.txt +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/lib/git_nested.egg-info/top_level.txt +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/setup.cfg +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/share/enable-completion.sh +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/scenarios/test_extraction.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/scenarios/test_filemode.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/scenarios/test_filter.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_branch.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_clean.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_completion.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_encode.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_fetch.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_gitignore.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_init.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_nested_in_nested.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_push.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_rebase.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_reclone.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_status.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/test_submodule.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/test/unittest/test_logging.py +0 -0
- {git_nested-1.0.4 → git_nested-1.0.5}/uv.lock +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,62 @@
|
|
|
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
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
build:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
# A PyInstaller binary links the glibc of the machine that built it, and
|
|
26
|
+
# glibc is only backward compatible -- built on ubuntu-latest it would
|
|
27
|
+
# refuse to start on anything older than that runner. AlmaLinux 8 has
|
|
28
|
+
# glibc 2.28, the oldest that still runs the node20 the actions below
|
|
29
|
+
# need, which covers every distro from Ubuntu 20.04 / Debian 10 /
|
|
30
|
+
# Fedora 29 / RHEL 8 onwards. glibc-based distros only -- musl ones
|
|
31
|
+
# (Alpine) can never run this binary.
|
|
32
|
+
container: almalinux:8
|
|
33
|
+
steps:
|
|
34
|
+
# Before the checkout -- without git, actions/checkout silently falls
|
|
35
|
+
# back to downloading a tarball, and a checkout with no .git has no
|
|
36
|
+
# tags for setuptools-scm to derive the version from.
|
|
37
|
+
- name: Install build prerequisites
|
|
38
|
+
run: dnf -y install python3.12 python3.12-pip git tar xz
|
|
39
|
+
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
with:
|
|
42
|
+
ref: ${{ inputs.ref }}
|
|
43
|
+
# the version frozen into the binary comes from the tags, and the
|
|
44
|
+
# default shallow checkout fetches none of them.
|
|
45
|
+
fetch-depth: 0
|
|
46
|
+
|
|
47
|
+
# the checkout is owned by a different user than this container's root,
|
|
48
|
+
# which git refuses to read from until told it is not a security issue.
|
|
49
|
+
- name: Trust the checkout
|
|
50
|
+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
51
|
+
|
|
52
|
+
- name: Build the executable
|
|
53
|
+
run: scripts/create-python-exe.sh --output dist --python python3.12
|
|
54
|
+
|
|
55
|
+
# the .tar.xz rather than the bare executable. It is what a release
|
|
56
|
+
# actually ships, and unlike a zipped artifact it keeps the exec bit,
|
|
57
|
+
# so consumers just untar and run it.
|
|
58
|
+
- uses: actions/upload-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
name: ${{ inputs.artifact }}
|
|
61
|
+
path: dist/git-nested_x64_Linux.tar.xz
|
|
62
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
name: Release binary
|
|
2
|
+
|
|
3
|
+
# Builds the single-file 'git-nested' executable (see
|
|
4
|
+
# scripts/create-python-exe.sh) and attaches git-nested_x64_Linux.tar.xz to the
|
|
5
|
+
# release, so git-nested can be used on a machine that has no python -- and no
|
|
6
|
+
# 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
|
+
ARCHIVE: git-nested_x64_Linux.tar.xz
|
|
38
|
+
|
|
39
|
+
jobs:
|
|
40
|
+
# the glibc floor and every other build decision live there, shared with
|
|
41
|
+
# test.yml so both exercise the same executable. 'env.REF' is not available
|
|
42
|
+
# to a reusable workflow's inputs, so the ref is spelled out again.
|
|
43
|
+
build:
|
|
44
|
+
uses: ./.github/workflows/build-binary.yml
|
|
45
|
+
with:
|
|
46
|
+
ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
|
|
47
|
+
artifact: git-nested-binary
|
|
48
|
+
|
|
49
|
+
# The whole pytest suite, driven through the binary that is about to be
|
|
50
|
+
# attached -- the check that it is functionally complete, not just startable.
|
|
51
|
+
test:
|
|
52
|
+
needs: build
|
|
53
|
+
uses: ./.github/workflows/test-binary.yml
|
|
54
|
+
with:
|
|
55
|
+
ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
|
|
56
|
+
artifact: git-nested-binary
|
|
57
|
+
|
|
58
|
+
# Proves the "runs on any x86_64 Linux" claim rather than assuming it. The
|
|
59
|
+
# binary is dropped into stock distro images that have no python installed,
|
|
60
|
+
# and has to drive a real nested repo end to end there.
|
|
61
|
+
verify:
|
|
62
|
+
needs: build
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
strategy:
|
|
65
|
+
fail-fast: false
|
|
66
|
+
matrix:
|
|
67
|
+
include:
|
|
68
|
+
# the glibc floor it was built against, and the oldest still-
|
|
69
|
+
# supported LTS releases -- the ones a modern-runner build breaks
|
|
70
|
+
- image: almalinux:8
|
|
71
|
+
install: dnf -y install git
|
|
72
|
+
- image: ubuntu:22.04
|
|
73
|
+
install: apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git
|
|
74
|
+
- image: debian:12
|
|
75
|
+
install: apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git
|
|
76
|
+
# ... plus the current end of the range, where a binary built too
|
|
77
|
+
# old would fail instead
|
|
78
|
+
- image: ubuntu:24.04
|
|
79
|
+
install: apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git
|
|
80
|
+
- image: fedora:latest
|
|
81
|
+
install: dnf -y install git
|
|
82
|
+
- image: archlinux:latest
|
|
83
|
+
install: pacman -Sy --noconfirm git
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/download-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: git-nested-binary
|
|
88
|
+
|
|
89
|
+
- name: Unpack
|
|
90
|
+
run: tar -xf "$ARCHIVE"
|
|
91
|
+
|
|
92
|
+
# git is the one thing that does have to be installed -- git-nested
|
|
93
|
+
# drives it and refuses to start without it. Python deliberately is not:
|
|
94
|
+
# if any of these images had to install it, the binary would not be
|
|
95
|
+
# doing its job.
|
|
96
|
+
- name: Write the verification script
|
|
97
|
+
run: |
|
|
98
|
+
cat > verify.sh <<'EOF'
|
|
99
|
+
set -eu
|
|
100
|
+
if command -v python3 >/dev/null; then
|
|
101
|
+
echo "note: this image happens to ship python3; the binary does not use it"
|
|
102
|
+
else
|
|
103
|
+
echo "note: no python3 in this image at all"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
git-nested --version
|
|
107
|
+
|
|
108
|
+
# invoked as `git nested`, the way users run it: git finds the
|
|
109
|
+
# subcommand by looking for a 'git-nested' on PATH.
|
|
110
|
+
git config --global user.email ci@example.com
|
|
111
|
+
git config --global user.name CI
|
|
112
|
+
git config --global init.defaultBranch master
|
|
113
|
+
git config --global --add safe.directory '*'
|
|
114
|
+
|
|
115
|
+
mkdir -p /tmp/repo/doc
|
|
116
|
+
cd /tmp/repo
|
|
117
|
+
git init -q
|
|
118
|
+
touch doc/README.md
|
|
119
|
+
git add -A
|
|
120
|
+
git commit -qm "initial"
|
|
121
|
+
|
|
122
|
+
git nested init doc
|
|
123
|
+
test -f doc/.gitnested
|
|
124
|
+
cat doc/.gitnested
|
|
125
|
+
git nested status
|
|
126
|
+
EOF
|
|
127
|
+
|
|
128
|
+
# The binary and the script are mounted in, so the distro images need
|
|
129
|
+
# neither tar/xz nor anything else to take part beyond git -- the point
|
|
130
|
+
# is that nothing else is required.
|
|
131
|
+
- name: Run on ${{ matrix.image }}
|
|
132
|
+
run: |
|
|
133
|
+
docker run --rm \
|
|
134
|
+
-v "$PWD/git-nested:/usr/local/bin/git-nested:ro" \
|
|
135
|
+
-v "$PWD/verify.sh:/verify.sh:ro" \
|
|
136
|
+
${{ matrix.image }} \
|
|
137
|
+
sh -c '${{ matrix.install }} >/dev/null && sh /verify.sh'
|
|
138
|
+
|
|
139
|
+
upload:
|
|
140
|
+
needs: [test, verify]
|
|
141
|
+
# nothing to attach when workflow_dispatch ran without a tag
|
|
142
|
+
if: github.event_name == 'release' || inputs.tag != ''
|
|
143
|
+
runs-on: ubuntu-latest
|
|
144
|
+
permissions:
|
|
145
|
+
contents: write
|
|
146
|
+
steps:
|
|
147
|
+
- uses: actions/download-artifact@v4
|
|
148
|
+
with:
|
|
149
|
+
name: git-nested-binary
|
|
150
|
+
|
|
151
|
+
# --clobber so re-running against an already-attached release replaces
|
|
152
|
+
# the asset instead of failing halfway.
|
|
153
|
+
- name: Attach ${{ env.ARCHIVE }} to ${{ env.REF }}
|
|
154
|
+
env:
|
|
155
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
156
|
+
GH_REPO: ${{ github.repository }}
|
|
157
|
+
run: gh release upload "$REF" "$ARCHIVE" --clobber
|
|
@@ -0,0 +1,78 @@
|
|
|
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.
|
|
65
|
+
- name: Unpack the binary
|
|
66
|
+
run: |
|
|
67
|
+
mkdir -p bin
|
|
68
|
+
tar -C bin -xf git-nested_x64_Linux.tar.xz
|
|
69
|
+
bin/git-nested --version
|
|
70
|
+
|
|
71
|
+
# no coverage flags: the code being executed lives in another process
|
|
72
|
+
# inside the frozen archive, so there is nothing here for coverage to
|
|
73
|
+
# measure. test.yml's module run is what the coverage numbers come from.
|
|
74
|
+
- name: Run the suite against the binary
|
|
75
|
+
env:
|
|
76
|
+
GIT_NESTED_EXE: ${{ github.workspace }}/bin/git-nested
|
|
77
|
+
PYTHONIOENCODING: utf-8
|
|
78
|
+
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:
|
|
@@ -65,6 +65,31 @@ 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
95
|
We use [Ruff](https://github.com/astral-sh/ruff) for both linting and formatting:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-nested
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: Git Submodule Alternative
|
|
5
5
|
Author-email: Thorsten Klein <thorsten.klein.git@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -80,7 +80,25 @@ 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:
|
|
83
|
+
#### Method 2: Standalone Executable (no Python needed)
|
|
84
|
+
|
|
85
|
+
Every [release](https://github.com/thorsten-klein/git-nested/releases) ships
|
|
86
|
+
`git-nested_x64_Linux.tar.xz`: a single file with git-nested, its dependencies
|
|
87
|
+
and a Python interpreter inside. Only git itself has to be installed.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
curl -L https://github.com/thorsten-klein/git-nested/releases/latest/download/git-nested_x64_Linux.tar.xz | tar -xJ
|
|
91
|
+
sudo install git-nested /usr/local/bin/ # any directory on PATH works
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The file has to keep the name `git-nested` — that is how `git nested ...`
|
|
95
|
+
finds it. The archive also contains the completion scripts from `share/`.
|
|
96
|
+
|
|
97
|
+
Built on x86_64 with glibc 2.28, so it runs on any x86_64 Linux from that
|
|
98
|
+
version onwards (Ubuntu 20.04, Debian 10, RHEL/AlmaLinux 8 and newer). Not on
|
|
99
|
+
musl distros such as Alpine.
|
|
100
|
+
|
|
101
|
+
#### Method 3: From Source with Shell Integration
|
|
84
102
|
|
|
85
103
|
Adds git-nested to PATH and enables tab completion:
|
|
86
104
|
|
|
@@ -90,7 +108,7 @@ echo 'source /path/to/git-nested/.rc' >> ~/.bashrc
|
|
|
90
108
|
source ~/.bashrc
|
|
91
109
|
```
|
|
92
110
|
|
|
93
|
-
#### Method
|
|
111
|
+
#### Method 4: From Source (Manual)
|
|
94
112
|
|
|
95
113
|
```bash
|
|
96
114
|
git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
|
|
@@ -195,6 +213,22 @@ Fetch remote content for a nested repository.
|
|
|
195
213
|
git nested fetch --help
|
|
196
214
|
```
|
|
197
215
|
|
|
216
|
+
#### `git nested diff`
|
|
217
|
+
|
|
218
|
+
Show the local diff of a nested repo compared to upstream. Fetches the latest upstream
|
|
219
|
+
content first, so it always compares against the freshest available state. Requires that
|
|
220
|
+
there are no uncommitted changes in the outer repository.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
git nested diff --help
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Example:**
|
|
227
|
+
```bash
|
|
228
|
+
git nested diff ext/lib
|
|
229
|
+
git nested diff --all # Diff all nested repos
|
|
230
|
+
```
|
|
231
|
+
|
|
198
232
|
#### `git nested branch`
|
|
199
233
|
|
|
200
234
|
Create a branch with local nested commits for manual conflict resolution.
|
|
@@ -54,7 +54,25 @@ 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:
|
|
57
|
+
#### Method 2: Standalone Executable (no Python needed)
|
|
58
|
+
|
|
59
|
+
Every [release](https://github.com/thorsten-klein/git-nested/releases) ships
|
|
60
|
+
`git-nested_x64_Linux.tar.xz`: a single file with git-nested, its dependencies
|
|
61
|
+
and a Python interpreter inside. Only git itself has to be installed.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
curl -L https://github.com/thorsten-klein/git-nested/releases/latest/download/git-nested_x64_Linux.tar.xz | tar -xJ
|
|
65
|
+
sudo install git-nested /usr/local/bin/ # any directory on PATH works
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The file has to keep the name `git-nested` — that is how `git nested ...`
|
|
69
|
+
finds it. The archive also contains the completion scripts from `share/`.
|
|
70
|
+
|
|
71
|
+
Built on x86_64 with glibc 2.28, so it runs on any x86_64 Linux from that
|
|
72
|
+
version onwards (Ubuntu 20.04, Debian 10, RHEL/AlmaLinux 8 and newer). Not on
|
|
73
|
+
musl distros such as Alpine.
|
|
74
|
+
|
|
75
|
+
#### Method 3: From Source with Shell Integration
|
|
58
76
|
|
|
59
77
|
Adds git-nested to PATH and enables tab completion:
|
|
60
78
|
|
|
@@ -64,7 +82,7 @@ echo 'source /path/to/git-nested/.rc' >> ~/.bashrc
|
|
|
64
82
|
source ~/.bashrc
|
|
65
83
|
```
|
|
66
84
|
|
|
67
|
-
#### Method
|
|
85
|
+
#### Method 4: From Source (Manual)
|
|
68
86
|
|
|
69
87
|
```bash
|
|
70
88
|
git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
|
|
@@ -169,6 +187,22 @@ Fetch remote content for a nested repository.
|
|
|
169
187
|
git nested fetch --help
|
|
170
188
|
```
|
|
171
189
|
|
|
190
|
+
#### `git nested diff`
|
|
191
|
+
|
|
192
|
+
Show the local diff of a nested repo compared to upstream. Fetches the latest upstream
|
|
193
|
+
content first, so it always compares against the freshest available state. Requires that
|
|
194
|
+
there are no uncommitted changes in the outer repository.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git nested diff --help
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Example:**
|
|
201
|
+
```bash
|
|
202
|
+
git nested diff ext/lib
|
|
203
|
+
git nested diff --all # Diff all nested repos
|
|
204
|
+
```
|
|
205
|
+
|
|
172
206
|
#### `git nested branch`
|
|
173
207
|
|
|
174
208
|
Create a branch with local nested commits for manual conflict resolution.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-nested
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: Git Submodule Alternative
|
|
5
5
|
Author-email: Thorsten Klein <thorsten.klein.git@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -80,7 +80,25 @@ 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:
|
|
83
|
+
#### Method 2: Standalone Executable (no Python needed)
|
|
84
|
+
|
|
85
|
+
Every [release](https://github.com/thorsten-klein/git-nested/releases) ships
|
|
86
|
+
`git-nested_x64_Linux.tar.xz`: a single file with git-nested, its dependencies
|
|
87
|
+
and a Python interpreter inside. Only git itself has to be installed.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
curl -L https://github.com/thorsten-klein/git-nested/releases/latest/download/git-nested_x64_Linux.tar.xz | tar -xJ
|
|
91
|
+
sudo install git-nested /usr/local/bin/ # any directory on PATH works
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The file has to keep the name `git-nested` — that is how `git nested ...`
|
|
95
|
+
finds it. The archive also contains the completion scripts from `share/`.
|
|
96
|
+
|
|
97
|
+
Built on x86_64 with glibc 2.28, so it runs on any x86_64 Linux from that
|
|
98
|
+
version onwards (Ubuntu 20.04, Debian 10, RHEL/AlmaLinux 8 and newer). Not on
|
|
99
|
+
musl distros such as Alpine.
|
|
100
|
+
|
|
101
|
+
#### Method 3: From Source with Shell Integration
|
|
84
102
|
|
|
85
103
|
Adds git-nested to PATH and enables tab completion:
|
|
86
104
|
|
|
@@ -90,7 +108,7 @@ echo 'source /path/to/git-nested/.rc' >> ~/.bashrc
|
|
|
90
108
|
source ~/.bashrc
|
|
91
109
|
```
|
|
92
110
|
|
|
93
|
-
#### Method
|
|
111
|
+
#### Method 4: From Source (Manual)
|
|
94
112
|
|
|
95
113
|
```bash
|
|
96
114
|
git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
|
|
@@ -195,6 +213,22 @@ Fetch remote content for a nested repository.
|
|
|
195
213
|
git nested fetch --help
|
|
196
214
|
```
|
|
197
215
|
|
|
216
|
+
#### `git nested diff`
|
|
217
|
+
|
|
218
|
+
Show the local diff of a nested repo compared to upstream. Fetches the latest upstream
|
|
219
|
+
content first, so it always compares against the freshest available state. Requires that
|
|
220
|
+
there are no uncommitted changes in the outer repository.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
git nested diff --help
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Example:**
|
|
227
|
+
```bash
|
|
228
|
+
git nested diff ext/lib
|
|
229
|
+
git nested diff --all # Diff all nested repos
|
|
230
|
+
```
|
|
231
|
+
|
|
198
232
|
#### `git nested branch`
|
|
199
233
|
|
|
200
234
|
Create a branch with local nested commits for manual conflict resolution.
|
|
@@ -8,7 +8,11 @@ LICENSE
|
|
|
8
8
|
README.md
|
|
9
9
|
pyproject.toml
|
|
10
10
|
uv.lock
|
|
11
|
+
.github/CODEOWNERS
|
|
12
|
+
.github/workflows/build-binary.yml
|
|
11
13
|
.github/workflows/publish.yml
|
|
14
|
+
.github/workflows/release-binary.yml
|
|
15
|
+
.github/workflows/test-binary.yml
|
|
12
16
|
.github/workflows/test.yml
|
|
13
17
|
docs/diagrams.md
|
|
14
18
|
lib/git-nested
|
|
@@ -19,6 +23,7 @@ lib/git_nested.egg-info/dependency_links.txt
|
|
|
19
23
|
lib/git_nested.egg-info/entry_points.txt
|
|
20
24
|
lib/git_nested.egg-info/requires.txt
|
|
21
25
|
lib/git_nested.egg-info/top_level.txt
|
|
26
|
+
scripts/create-python-exe.sh
|
|
22
27
|
share/completion.bash
|
|
23
28
|
share/enable-completion.sh
|
|
24
29
|
share/git-nested.fish
|
|
@@ -28,6 +33,7 @@ test/test_branch.py
|
|
|
28
33
|
test/test_clean.py
|
|
29
34
|
test/test_clone.py
|
|
30
35
|
test/test_completion.py
|
|
36
|
+
test/test_diff.py
|
|
31
37
|
test/test_encode.py
|
|
32
38
|
test/test_error.py
|
|
33
39
|
test/test_fetch.py
|
|
@@ -668,6 +668,30 @@ class GitNestedRepo:
|
|
|
668
668
|
|
|
669
669
|
return True, branch_name, subdir_worktree, branch_created, new_commit
|
|
670
670
|
|
|
671
|
+
def get_diff(self, git: GitRunner, flags: Flags, config: NestedConfig, subdir: Path, subref: str) -> str:
|
|
672
|
+
"""Compute the diff between the local nested repository content and the
|
|
673
|
+
freshly fetched upstream content.
|
|
674
|
+
|
|
675
|
+
Returns:
|
|
676
|
+
diff text (empty string if there are no differences)
|
|
677
|
+
"""
|
|
678
|
+
upstream_head_commit = self.do_fetch(git, flags, config, subref)
|
|
679
|
+
|
|
680
|
+
local_tree = git.check_output(['rev-parse', f'HEAD:{subdir}'])
|
|
681
|
+
|
|
682
|
+
if config.filter:
|
|
683
|
+
upstream_target = self.build_filtered_commit(git, Path.cwd(), config, upstream_head_commit)
|
|
684
|
+
else:
|
|
685
|
+
upstream_target = upstream_head_commit
|
|
686
|
+
|
|
687
|
+
return git.check_output([
|
|
688
|
+
'diff',
|
|
689
|
+
local_tree,
|
|
690
|
+
upstream_target,
|
|
691
|
+
'--',
|
|
692
|
+
':(exclude,glob)**/.gitnested*',
|
|
693
|
+
])
|
|
694
|
+
|
|
671
695
|
def do_fetch(self, git: GitRunner, flags: Flags, config: NestedConfig, subref: str) -> str:
|
|
672
696
|
"""Fetch upstream content
|
|
673
697
|
|
|
@@ -1291,7 +1315,7 @@ class GitNestedRepo:
|
|
|
1291
1315
|
|
|
1292
1316
|
def check_worktree_clean(self, git: GitRunner, command: str):
|
|
1293
1317
|
"""Ensure working copy has no uncommitted changes"""
|
|
1294
|
-
if command not in ['clone', 'init', 'pull', 'push', 'branch', 'commit']:
|
|
1318
|
+
if command not in ['clone', 'init', 'pull', 'push', 'branch', 'commit', 'diff']:
|
|
1295
1319
|
return
|
|
1296
1320
|
|
|
1297
1321
|
pwd = Path.cwd()
|
|
@@ -1496,6 +1520,7 @@ class GitNestedCommand:
|
|
|
1496
1520
|
'clean': ['ALL', 'all', 'force'],
|
|
1497
1521
|
'clone': ['branch', 'filter', 'force', 'message', 'method', 'filter'],
|
|
1498
1522
|
'commit': ['fetch', 'force', 'message', 'msg_file'],
|
|
1523
|
+
'diff': ['all', 'branch', 'remote'],
|
|
1499
1524
|
'fetch': ['all', 'branch', 'force', 'remote'],
|
|
1500
1525
|
'init': ['branch', 'remote', 'method'],
|
|
1501
1526
|
'pull': ['all', 'branch', 'force', 'message', 'method', 'remote', 'update'],
|
|
@@ -1543,7 +1568,7 @@ class GitNestedCommand:
|
|
|
1543
1568
|
add_subparser_args(command_subparser, command)
|
|
1544
1569
|
|
|
1545
1570
|
# Few commands also accept positional args
|
|
1546
|
-
if command in ['branch', 'commit', 'fetch', 'init', 'pull', 'push']:
|
|
1571
|
+
if command in ['branch', 'commit', 'diff', 'fetch', 'init', 'pull', 'push']:
|
|
1547
1572
|
command_subparser.add_argument('subdir', nargs='?')
|
|
1548
1573
|
if command in ['clean']:
|
|
1549
1574
|
command_subparser.add_argument('subdir', nargs='?')
|
|
@@ -1630,6 +1655,7 @@ class GitNestedCommand:
|
|
|
1630
1655
|
'pull': lambda: self.cmd_pull(flags, subdir, upstream, nested_commit_ref, git_tmp, head_commit),
|
|
1631
1656
|
'push': lambda: self.cmd_push(flags, subdir, upstream, nested_commit_ref, git_tmp, head_commit),
|
|
1632
1657
|
'fetch': lambda: self.cmd_fetch(flags, subdir, upstream),
|
|
1658
|
+
'diff': lambda: self.cmd_diff(flags, subdir, upstream),
|
|
1633
1659
|
'branch': lambda: self.cmd_branch(flags, subdir, upstream, git_tmp),
|
|
1634
1660
|
'commit': lambda: self.cmd_commit(flags, subdir, upstream, nested_commit_ref, git_tmp, head_commit),
|
|
1635
1661
|
'status': lambda: self.cmd_status(flags, git_tmp),
|
|
@@ -1886,6 +1912,21 @@ class GitNestedCommand:
|
|
|
1886
1912
|
self.repo.do_fetch(self.git, flags, config, subref)
|
|
1887
1913
|
self.say(f"Fetched '{subdir}' from '{config.remote}' ({config.branch}).", flags)
|
|
1888
1914
|
|
|
1915
|
+
def cmd_diff(self, flags, subdir, upstream):
|
|
1916
|
+
"""Show the local diff of a nested repo compared to upstream"""
|
|
1917
|
+
subdir, gitnested, subref, config = self.setup_command('diff', flags, subdir, upstream)
|
|
1918
|
+
|
|
1919
|
+
if config.remote == 'none':
|
|
1920
|
+
self.say(f"Ignored '{subdir}', no remote.", flags)
|
|
1921
|
+
return
|
|
1922
|
+
|
|
1923
|
+
diff_output = self.repo.get_diff(self.git, flags, config, subdir, subref)
|
|
1924
|
+
|
|
1925
|
+
if not diff_output:
|
|
1926
|
+
self.say(f"No differences between '{subdir}' and upstream '{config.remote}' ({config.branch}).", flags)
|
|
1927
|
+
else:
|
|
1928
|
+
self.say(diff_output, flags)
|
|
1929
|
+
|
|
1889
1930
|
def cmd_branch(self, flags, subdir, upstream, git_tmp):
|
|
1890
1931
|
"""Create a branch containing the local nested repo commits"""
|
|
1891
1932
|
subdir, gitnested, subref, config = self.setup_command('branch', flags, subdir, upstream)
|