Skip to content

feat: add 'uvbox git' subcommand for git repository sources (#19)#22

Open
hasansezertasan wants to merge 18 commits into
AmadeusITGroup:mainfrom
hasansezertasan:feat/git-subcommand
Open

feat: add 'uvbox git' subcommand for git repository sources (#19)#22
hasansezertasan wants to merge 18 commits into
AmadeusITGroup:mainfrom
hasansezertasan:feat/git-subcommand

Conversation

@hasansezertasan

@hasansezertasan hasansezertasan commented Apr 10, 2026

Copy link
Copy Markdown

Closes #19.

Summary

Adds a new uvbox git <git-spec> subcommand alongside the existing pypi and wheel source types. The git spec is passed through to uv tool install --from verbatim, so any form uv accepts works (@main, @v1.0.0, @<commit>, ssh://, etc.).

uvbox git git+https://github.com/org/repo
uvbox git git+https://github.com/org/repo@v1.0.0
uvbox git git+ssh://git@github.com/org/private-repo

Implements the approach @Coruscant11 chose in this comment: subcommand with the git spec as a positional CLI argument, no changes to the uvbox.toml schema.

When the outer version check decides to install/update, uvToolInstallGit runs uv tool install --from <spec> <name> --upgrade. auto-update = true with no [package.version] set re-enters that path on every run (the version compare treats the unset case as always-stale), delivering the pycrucible delete_after_run-equivalent behavior the issue requested. If a user sets [package.version].static on a git build, a user-visible warning is logged because the git ref is the source of truth.

Design notes

  • Additive only. The pypi and wheel leaf install functions are untouched. A regression test in box/config_identifier_test.go locks in a byte-stable golden hash for ComputeIdentifier to guarantee existing binaries find the same XDG dir.
  • Distinct identifier per git source. The git spec participates in ComputeIdentifier (only when set), so binaries built from git+url@main and git+url@v1.0.0 get separate isolated install dirs. No cross-contamination with pypi-built binaries of the same package.
  • Source embedding via file, not ldflag. See "Pre-existing bug discovered" below.
  • Validation: must start with git+, the URL after the prefix must parse, the scheme must be one of http/https/ssh/file, and non-file schemes must have a host. Semantic ref resolution (branch/tag/commit existence) is still delegated to uv on first run.
  • Git and embedded wheels are mutually exclusive. Enforced both at build time (boxer preRun) and runtime (pure selectInstallMethod helper in box/box_package.go returns an error when both GIT_SOURCE and INSTALL_WHEELS are set).

Pre-existing bug discovered (and worked around)

The original spec proposed embedding the git source via -ldflags "-X main.GIT_SOURCE=...". The smoke test surfaced that this does not work because of a pre-existing bug introduced in #14 (commit f19680f, "fix: Use environment variables for ldflags"):

The Go toolchain splits GOFLAGS on whitespace. Any ldflag string containing -X main.foo=bar is split across flag boundaries, and the toolchain rejects -X as an unknown top-level flag with go: parsing $GOFLAGS: unknown flag -X.

This silently broke the uvbox wheel path in main since #14 was merged — it sets -X main.INSTALL_WHEELS=yes, which fails the same way. No test exercises wheel mode end-to-end so nobody noticed. Reproduction:

GOFLAGS='-ldflags=-s -w -X main.INSTALL_WHEELS=yes' go build -o hello .
# go: parsing $GOFLAGS: unknown flag -X

This PR does not fix the wheel bug — touching it requires re-validating the original Windows-via-uvx fix from #14 on Windows, which is out of scope here. The wheel path remains broken in main and should be addressed in a separate PR.

For git, this PR sidesteps GOFLAGS entirely by embedding the source in a small file (box/git_source.txt) read at compile time via //go:embed. boxer's writeGitSourceFile writes the spec into this file before go build. Empty file = pypi/wheel build, non-empty = git build. Same end result, different transport.

Files

Runtime (box/):

  • box/box_package_git.go (new) — GIT_SOURCE (loaded from embedded git_source.txt), uvToolInstallGit (accepts packageVersion for signature parity, warns and drops it since the git ref is authoritative; captures uv stdout into the returned error on failure), buildUvToolInstallFromArgs pure helper
  • box/box_package.go — pure selectInstallMethod(gitSource, installWheels) helper dispatches to pypi/wheels/git; rejects the git+wheels combination at runtime. InstallPackage now propagates downloadTemporaryFile errors instead of swallowing them at Debug level (pre-existing silent failure fixed while on the hot path)
  • box/config.goComputeIdentifier conditionally appends GIT_SOURCE (no-op when empty)
  • box/generate.go — adds empty git_source.txt placeholder generation, mirroring the existing wheels/placeholder pattern
  • box/config_identifier_test.go (new) — golden-hash regression test (with regeneration recipe) + git distinction tests
  • box/box_package_git_test.go (new) — pure-helper command-shape tests
  • box/box_package_test.go (new) — TestSelectInstallMethod dispatch matrix (pypi/wheels/git × default/conflict)

Build-time (boxer/):

  • boxer/git.go (new) — buildGoBuildLdflags pure helper (without git, since git uses file embed), validateGitSource (URL-parsing, scheme whitelist, host check), writeGitSourceFile
  • boxer/main.go — new gitCmd cobra command, GitSource CLI var, validateGitSourceFlag in preRun (also enforces mutual exclusion with WheelsToEmbed and prints helpful hints on invalid input), writeGitSourceFile call in insertFilesIntoBoxRepository
  • boxer/git_test.go (new) — ldflag regression tests for pypi/wheel, writeGitSourceFile tests, validateGitSource positive cases, TestValidateGitSource_RejectsMalformed (scheme typos, prefix-only, missing host), TestValidateGitSourceFlag_EmptyIsNoop (guards the "pypi/wheel builds unaffected" contract)

Docs:

  • README.md — feature bullet updated, new "Build from a Git Repository" section, behavior of [package.version] for git builds, private repo auth note, runtime git requirement
  • examples/git/simple-app.toml (new) — minimal runnable example using VaasuDevanS/cowsay-python
  • .gitignore — adds box/git_source.txt

Test Plan

  • Full box test suite passes (go test ./...) including the ComputeIdentifier golden-hash regression guard and the new selectInstallMethod dispatch matrix
  • Full boxer test suite passes including ldflag regression guards for the existing pypi/wheel paths and the new validateGitSource URL-parsing cases
  • go vet ./... clean in both modules
  • Manual smoke test: uvbox git git+https://github.com/VaasuDevanS/cowsay-python --darwin --arm builds, the resulting binary runs end-to-end (./cowsay -t "hi" prints the cow), self update reports "Already up-to-date", self path shows a git-distinct identifier
  • Regression: uvbox pypi still works, produces a different identifier hash (separate XDG dir from the git build of the same package)

Reviewers can reproduce the smoke test using the committed example config:

# Build boxer from source
cd boxer && go generate && go build -o /tmp/uvbox . && cd ..

# Build a cowsay binary straight from the git repo using the example config
/tmp/uvbox git git+https://github.com/VaasuDevanS/cowsay-python \
    --config examples/git/simple-app.toml \
    --darwin --arm   # adjust --darwin/--linux/--windows and --arm/--amd to your host

# Run the produced artifact
cd dist && tar xzf cowsay-*.tar.gz && ./cowsay -t "git works!"

See examples/git/simple-app.toml for the config, including the commented-out [package.version] auto-update = true block that flips the binary into "always re-resolve the git ref on every run" mode.

Update — review round 1 (commit a38fe58)

Addressed findings from a multi-agent PR review pass. All changes are additive to the original design:

  • Propagate constraints-file download error (pre-existing silent failure in InstallPackage, now on the git hot path)
  • Reject GIT_SOURCE + INSTALL_WHEELS conflict at build time (boxer preRun) and runtime (selectInstallMethod)
  • Warn when [package.version] is set on a git builduvToolInstallGit logs logger.Warn instead of silently ignoring
  • Capture uv stdout into error messages on git install failure, so resolver/auth output isn't discarded
  • Deepen validateGitSource with net/url parsing — reject scheme typos, unsupported schemes, and missing hosts at build time instead of on end-user machines
  • New tests: box/box_package_test.go (dispatch matrix) and expanded boxer/git_test.go (malformed-spec cases + empty-flag no-op guard)
  • Doc reconciliation: README [package.version] section, golden-hash regeneration recipe, trimmed duplicated inline comments

🤖 Generated with Claude Code

@Coruscant11

Copy link
Copy Markdown
Contributor

Gonna have a look tomorrow!

@hasansezertasan

Copy link
Copy Markdown
Author

Gonna have a look tomorrow!

Don't hurry, it's still in draft 🤓.

hasansezertasan added a commit to hasansezertasan/uvbox that referenced this pull request Apr 11, 2026
…behavior

- Surface all three source subcommands (pypi/wheel/git) in Basic Usage
  so readers don't need to scroll to discover git support.
- Use the real VaasuDevanS/cowsay-python repo in git command examples
  so they are copy-paste runnable against examples/git/simple-app.toml.
- Generalise [package].name / script reference docs: they apply to all
  source types, and for git the name must match the distribution name
  the repo's pyproject.toml registers (not the repo slug).
- Clarify auto-update semantics for git builds: always-update behavior
  only holds when [package.version] is unset; pinning static/dynamic to
  the installed version disables it via the version-compare short-circuit
  in box/main.go.
- Disambiguate Examples list entries by including the directory prefix
  (both simple-app.toml files were rendering with identical link text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hasansezertasan added a commit to hasansezertasan/uvbox that referenced this pull request Apr 11, 2026
- Propagate constraints-file download error instead of swallowing at Debug
- Extract selectInstallMethod pure helper; reject git+wheels conflict at
  both build time (boxer preRun) and runtime (box dispatch)
- uvToolInstallGit now takes packageVersion and warns when non-empty, so
  users who set [package.version].static on a git build get a visible
  breadcrumb instead of silent ignore
- Capture uv stdout into a buffer in uvToolInstallGit and include it in
  the returned error, preserving resolver/auth context on failure
- Deepen validateGitSource with net/url parsing: reject prefix-only,
  scheme typos, unsupported schemes, and missing host
- Add TestSelectInstallMethod dispatch matrix, malformed-spec validator
  cases, and a no-op guard test for validateGitSourceFlag
- Reconcile README [package.version] section with actual dispatch flow,
  add golden-hash regeneration recipe, and trim duplicated comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hasansezertasan

Copy link
Copy Markdown
Author

I pushed quickly today to get this PR ready. I'm not actively working with Go day-to-day — I put this together by reading through the surrounding code and iterating with an AI agent. There may be edge cases I missed, so I'd really appreciate a careful review. Thanks again!

@Coruscant11

@hasansezertasan hasansezertasan marked this pull request as ready for review April 12, 2026 12:09
Coruscant11 pushed a commit to hasansezertasan/uvbox that referenced this pull request Apr 12, 2026
…behavior

- Surface all three source subcommands (pypi/wheel/git) in Basic Usage
  so readers don't need to scroll to discover git support.
- Use the real VaasuDevanS/cowsay-python repo in git command examples
  so they are copy-paste runnable against examples/git/simple-app.toml.
- Generalise [package].name / script reference docs: they apply to all
  source types, and for git the name must match the distribution name
  the repo's pyproject.toml registers (not the repo slug).
- Clarify auto-update semantics for git builds: always-update behavior
  only holds when [package.version] is unset; pinning static/dynamic to
  the installed version disables it via the version-compare short-circuit
  in box/main.go.
- Disambiguate Examples list entries by including the directory prefix
  (both simple-app.toml files were rendering with identical link text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Coruscant11 pushed a commit to hasansezertasan/uvbox that referenced this pull request Apr 12, 2026
- Propagate constraints-file download error instead of swallowing at Debug
- Extract selectInstallMethod pure helper; reject git+wheels conflict at
  both build time (boxer preRun) and runtime (box dispatch)
- uvToolInstallGit now takes packageVersion and warns when non-empty, so
  users who set [package.version].static on a git build get a visible
  breadcrumb instead of silent ignore
- Capture uv stdout into a buffer in uvToolInstallGit and include it in
  the returned error, preserving resolver/auth context on failure
- Deepen validateGitSource with net/url parsing: reject prefix-only,
  scheme typos, unsupported schemes, and missing host
- Add TestSelectInstallMethod dispatch matrix, malformed-spec validator
  cases, and a no-op guard test for validateGitSourceFlag
- Reconcile README [package.version] section with actual dispatch flow,
  add golden-hash regeneration recipe, and trim duplicated comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Coruscant11 Coruscant11 force-pushed the feat/git-subcommand branch from a38fe58 to edc2339 Compare April 12, 2026 15:40

@Coruscant11 Coruscant11 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am starting to review. First thank for this PR, looking at it, I am just currently working on improving CI and tests, and fix the wheels issue your PR mentionned (really appreciate the catch).

Goal is to fix the issues that are bothering your PR so that we can have a good base.

Comment thread box/box_package.go Outdated
Comment thread box/box_package_git.go Outdated
Comment thread box/box_package_git.go Outdated
Comment thread box/box_package_git.go Outdated
Coruscant11 pushed a commit to hasansezertasan/uvbox that referenced this pull request Apr 12, 2026
…behavior

- Surface all three source subcommands (pypi/wheel/git) in Basic Usage
  so readers don't need to scroll to discover git support.
- Use the real VaasuDevanS/cowsay-python repo in git command examples
  so they are copy-paste runnable against examples/git/simple-app.toml.
- Generalise [package].name / script reference docs: they apply to all
  source types, and for git the name must match the distribution name
  the repo's pyproject.toml registers (not the repo slug).
- Clarify auto-update semantics for git builds: always-update behavior
  only holds when [package.version] is unset; pinning static/dynamic to
  the installed version disables it via the version-compare short-circuit
  in box/main.go.
- Disambiguate Examples list entries by including the directory prefix
  (both simple-app.toml files were rendering with identical link text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Coruscant11 pushed a commit to hasansezertasan/uvbox that referenced this pull request Apr 12, 2026
- Propagate constraints-file download error instead of swallowing at Debug
- Extract selectInstallMethod pure helper; reject git+wheels conflict at
  both build time (boxer preRun) and runtime (box dispatch)
- uvToolInstallGit now takes packageVersion and warns when non-empty, so
  users who set [package.version].static on a git build get a visible
  breadcrumb instead of silent ignore
- Capture uv stdout into a buffer in uvToolInstallGit and include it in
  the returned error, preserving resolver/auth context on failure
- Deepen validateGitSource with net/url parsing: reject prefix-only,
  scheme typos, unsupported schemes, and missing host
- Add TestSelectInstallMethod dispatch matrix, malformed-spec validator
  cases, and a no-op guard test for validateGitSourceFlag
- Reconcile README [package.version] section with actual dispatch flow,
  add golden-hash regeneration recipe, and trim duplicated comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Coruscant11 Coruscant11 force-pushed the feat/git-subcommand branch from edc2339 to f4e01f5 Compare April 12, 2026 17:37
hasansezertasan added a commit to hasansezertasan/uvbox that referenced this pull request Apr 13, 2026
- Revert constraints download to non-fatal logger.Debug (optional file)
- Unify log messages: "Installing package" with "method" key (pypi/git)
- Match uvToolInstallGit stdout/error handling to uvToolInstallLine pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hasansezertasan and others added 13 commits April 13, 2026 15:15
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts ldflag construction into a pure, testable function and adds
validateGitSource to enforce the git+ prefix constraint. Regression
tests lock in pypi and wheel ldflag output; new tests cover git source
injection behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oBuild

- Replace inline ldflags construction in goBuild with buildGoBuildLdflags(GitSource, WheelsToEmbed)
- Add GitSource package-level var alongside Config/Output/Nfpm/ReleaseVersion
- Register gitCmd cobra subcommand (ExactArgs(1), same flags as pypiCmd/wheelCmd)
- Add validateGitSourceFlag helper, called from preRun

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The smoke test surfaced a pre-existing bug in boxer's ldflag handling:
Go's GOFLAGS parser uses strings.Fields (whitespace split, no quote
handling), so any ldflag string containing -X flags fails to parse with
"unknown flag -X". This silently broke the wheel path in commit f19680f
when ldflags moved to GOFLAGS for Windows compatibility, and would have
broken git the same way.

To unblock the git feature without touching the wheel path (which
remains broken in main and needs a separate fix), embed the git source
via a committed placeholder file box/git_source.txt + //go:embed in
box/box_package_git.go. boxer writes the git spec into this file before
go build via writeGitSourceFile, replacing the empty placeholder for
git builds and leaving it empty for pypi/wheel builds.

This sidesteps GOFLAGS entirely for git. The wheel path's GOFLAGS bug
is left untouched and should be addressed in a separate PR by the
original author of f19680f, who can verify the Windows fix properly.

Verified end-to-end:
  uvbox git git+https://github.com/VaasuDevanS/cowsay-python --darwin --arm
  ./dist/cowsay -t "uvbox git works!"   # prints cow ✓
  ./cowsay self update                  # "Already up-to-date" ✓
  ./cowsay self path                    # cowsay-637049bd...  ✓
  uvbox pypi (regression)               # cowsay-2fea8a21... ✓ different dir

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match the existing wheels/placeholder pattern: gitignore the dynamically
generated file and create it on demand via go generate. Keeps the box
package free of empty committed files and lets `mise run generate:box`
handle bootstrapping for tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the VaasuDevanS/cowsay-python repo in examples/git/simple-app.toml
so the example is runnable as-is, and add a git entry to the README
examples list next to the existing pypi entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hasansezertasan and others added 4 commits April 13, 2026 15:15
…behavior

- Surface all three source subcommands (pypi/wheel/git) in Basic Usage
  so readers don't need to scroll to discover git support.
- Use the real VaasuDevanS/cowsay-python repo in git command examples
  so they are copy-paste runnable against examples/git/simple-app.toml.
- Generalise [package].name / script reference docs: they apply to all
  source types, and for git the name must match the distribution name
  the repo's pyproject.toml registers (not the repo slug).
- Clarify auto-update semantics for git builds: always-update behavior
  only holds when [package.version] is unset; pinning static/dynamic to
  the installed version disables it via the version-compare short-circuit
  in box/main.go.
- Disambiguate Examples list entries by including the directory prefix
  (both simple-app.toml files were rendering with identical link text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
uv shells out to the system git binary when resolving git+ sources, so
binaries produced by 'uvbox git' need git installed on the end-user
machine at first run (or whenever auto-update re-installs). Also call
out that ssh:// specs use the user's local SSH setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Propagate constraints-file download error instead of swallowing at Debug
- Extract selectInstallMethod pure helper; reject git+wheels conflict at
  both build time (boxer preRun) and runtime (box dispatch)
- uvToolInstallGit now takes packageVersion and warns when non-empty, so
  users who set [package.version].static on a git build get a visible
  breadcrumb instead of silent ignore
- Capture uv stdout into a buffer in uvToolInstallGit and include it in
  the returned error, preserving resolver/auth context on failure
- Deepen validateGitSource with net/url parsing: reject prefix-only,
  scheme typos, unsupported schemes, and missing host
- Add TestSelectInstallMethod dispatch matrix, malformed-spec validator
  cases, and a no-op guard test for validateGitSourceFlag
- Reconcile README [package.version] section with actual dispatch flow,
  add golden-hash regeneration recipe, and trim duplicated comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Revert constraints download to non-fatal logger.Debug (optional file)
- Unify log messages: "Installing package" with "method" key (pypi/git)
- Match uvToolInstallGit stdout/error handling to uvToolInstallLine pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hasansezertasan

Copy link
Copy Markdown
Author

I am starting to review. First thank for this PR, looking at it, I am just currently working on improving CI and tests, and fix the wheels issue your PR mentionned (really appreciate the catch).

Goal is to fix the issues that are bothering your PR so that we can have a good base.

Thanks for the review. I addressed your comments. Can you take a look at it again 🤓?

@Coruscant11

Coruscant11 commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Remains only to fix the wheel issue you reported on my side!
Then I can come back to this PR. Expect to clear this tomorrow.
Thanks again for your patience!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Support git URLs as package source for self-update

2 participants