Publishing from Git
club publish --from-git extends club publish so you
can publish a package from a git URL without cloning the repo yourself.
In one command it:
- Clones the repository into
~/.club/clones/<host>/<org>/<repo>. - Checks out a branch, tag, commit, or pull request (defaults to the remote default branch).
- Runs the standard publish flow on the clone.
- Deletes the clone on success.
- Keeps the clone on failure so a re-run reuses it via a fast hard reset, no re-clone.
Everything downstream of the clone is the unchanged single-package or
--auto flow. The on-disk state of your current shell is never touched.
Quick start
Publish the default branch of a public repo:
club publish --from-git https://github.com/your-org/your-packagePublish a specific tag or branch:
club publish --from-git https://github.com/your-org/your-package --ref v1.2.0club publish --from-git https://github.com/your-org/your-package --ref mainPublish the code on a GitHub pull request, as a prerelease:
club publish --from-git https://github.com/your-org/your-package/pull/2Publish a package that lives in a subdirectory of the repo:
club publish --from-git https://github.com/your-org/your-monorepo -C packages/fooPublish every package of a monorepo in topological order:
club publish --from-git https://github.com/your-org/your-monorepo --autoPreview without uploading:
club publish --from-git https://github.com/your-org/your-package --dry-runA typical successful run looks like:
Preparing git source ──────────────────────────────────────── repo: your-org/your-package (github.com) cache: /home/me/.club/clones/github.com/your-org/your-package cloning… ✓ cloned, checked out main (a1b2c3d) (1.4s)
📦 Publishing your_package 1.2.0 from /home/me/.club/clones/github.com/your-org/your-package to myclub.birju.dev (auto-selected (only logged-in server))
Building package archive ──────────────────────────────────── /tmp/club-publish-1779431135919322.tar.gz 12.2 KiB, 16 files (42ms)
Resolving dependencies ────────────────────────────────────── ✓ Resolved (1.2s)
Running 25 validators ─────────────────────────────────────── ✓ All validators passed. (642ms)
Uploading ─────────────────────────────────────────────────── Server: published your_package 1.2.0 (982ms)
┌──────────────────────────────────────────────────────┐ │ 🎉 your_package 1.2.0 published │ │ URL https://myclub.birju.dev/packages/your_package│ │ Size 12.2 KiB (16 files) │ └──────────────────────────────────────────────────────┘
Removing clone /home/me/.club/clones/github.com/your-org/your-packageHow it works
-
URL parsing. Both
https://host/org/repo(.git)and SCP-stylegit@host:org/repo.gitURLs are accepted. The host and repo path are mapped to a namespaced cache directory under~/.club/clones/so two repos with the same name from different orgs never collide. A GitHub pull request URL is reduced to its repository here, and the PR number is remembered for the next two steps. -
Clone or reuse (shallow). If the cache directory does not exist (or its
originremote does not match the requested URL, or the directory is not a healthy git work tree), Club does a fresh--depth 1clone of just the ref being published, on a single branch. If the cache directory is a healthy match, Club reuses it and runs a shallowgit fetchfor the same ref. Either way the cache holds only the single commit being published, no history. A pull request is fetched fromrefs/pull/<n>/headinto a localpr/<n>tracking branch, so from here on it behaves like any other branch. -
Checkout. Club force-checks-out the requested ref. Local branches are reset to the matching remote tip, tags and commits are checked out detached, and
git clean -ffdxremoves any untracked or ignored files (stale.dart_tool, lockfiles, build output). The working tree is pristine before the publish runs. -
Standard publish flow. From here on the run is identical to a regular
club publish(orclub publish --auto) executed in the clone directory: server resolution, version conflict pre-check, tarball build, standalone dependency resolution, the 25 validators, upload. -
Cleanup. After a successful publish the clone is deleted and any empty parent directories under
~/.club/clones/are pruned. If the publish fails the clone is kept so a re-run can fast-path through step 2 with no re-clone.
Monorepos: --auto and subdirectories
--from-git composes with both ways club publish handles monorepos:
Publish every package in a workspace-style monorepo in topological order, with in-memory dependency rewrites, in a single command:
club publish --from-git https://github.com/your-org/your-monorepo --autoThe full --auto pipeline runs against the
clone: discovery walks the cloned tree, the dependency graph and
topo order are computed, internal path: deps are rewritten in
memory, and every package is published in order. Target a specific
subset by passing package names as positional args:
club publish --from-git https://github.com/your-org/your-monorepo \ --auto pkg_a pkg_bPublish a single package that lives in a subdirectory of the repo,
without involving --auto:
club publish --from-git https://github.com/your-org/your-monorepo \ -C packages/foo-C (--directory) is resolved relative to the clone root when
--from-git is set, so you can point at any subdirectory inside the
repo. The single-package flow then runs as if you had cd’d into
that directory.
Pull requests
Pass a GitHub pull request URL and Club publishes the code on that PR:
club publish --from-git https://github.com/your-org/your-package/pull/2This is the review-before-merge case: get a contributor’s branch onto your private server so downstream projects can depend on it and try it for real, without merging first and without the PR author needing publish rights.
The derived version below is a default, not a constraint. The version step offers it and you can publish as anything else instead.
The version is derived, never the one in the pubspec
A PR is published as a prerelease of the package’s own version. If
pubspec.yaml on the PR says 1.2.0, PR #2 publishes as:
1.2.0-pr2The pubspec on disk is never modified. Only the copy inside the
uploaded tarball carries the derived version, exactly like
--version.
This matters because contributors almost never bump the version in a
PR. Without the suffix, publishing PR #2 of a package sitting at 1.2.0
would overwrite the real released 1.2.0 with unreviewed code. The
suffix makes that impossible.
It also means a PR build can never be picked up by accident. Under
semver a prerelease sorts below its release, so a consumer with
^1.2.0 will not resolve 1.2.0-pr2. Depending on a PR build has to
be deliberate:
dependencies: your_package: hosted: https://myclub.birju.dev version: 1.2.0-pr2How the suffix is applied in the less obvious cases:
| Version in the PR | Published as |
|---|---|
1.2.0 | 1.2.0-pr2 |
1.2.0-dev.3 | 1.2.0-dev.3.pr2 |
1.2.0+5 | 1.2.0-pr2+5 |
Validators run against the derived version, with one accommodation: the CHANGELOG check is satisfied by an entry for the version declared in the pubspec. Nobody writes a changelog entry for a version that only comes into existence at publish time.
Updating a PR build
The suffix is deliberately stable: it contains the PR number and
nothing else, no commit hash. Every publish of PR #2 produces
1.2.0-pr2. When the author pushes new commits, re-publish the same
version with --force:
club publish --from-git https://github.com/your-org/your-package/pull/2 --forceConsumers keep the constraint they already have and just re-resolve.
The alternative, encoding the commit into the version, would leave a
pile of dead 1.2.0-pr2.a1b2c3d versions nobody ever cleans up.
Accepted URL forms
Anything you can copy out of the browser address bar while looking at a PR works. Trailing tabs, query strings, and fragments are ignored:
https://github.com/your-org/your-package/pull/2https://github.com/your-org/your-package/pull/2/fileshttps://github.com/your-org/your-package/pull/2/commitshttps://github.com/your-org/your-package/pull/2#issuecomment-12345Club resolves the PR head from refs/pull/<n>/head, which GitHub
publishes on the repository itself. PRs opened from forks work with
no extra configuration, and the fork does not have to be reachable.
Monorepos
A PR against a monorepo composes with --auto, and the whole stack is
suffixed together:
club publish --from-git https://github.com/your-org/your-monorepo/pull/7 --autoEvery package in the closure publishes as X.Y.Z-pr7, and the
rewritten internal constraints point at the prereleases too. If
pkg_a depends on pkg_b, pkg_a is published with:
dependencies: pkg_b: hosted: https://myclub.birju.dev version: ^1.2.0-pr7So the published stack resolves against itself rather than falling back to the last released versions of its siblings. The rewrite preview printed before the upload shows the exact constraints, so you can check this before anything is sent.
Restrictions
| Combination | Result |
|---|---|
--ref with a PR URL | Rejected. The PR URL already selects the commit. |
--version with a PR URL | Publishes that version verbatim, with no -pr<n> suffix. See Choosing the version. |
-C, --auto, --dry-run, --force, --server | All work normally. |
Choosing the version
Publishing from git is usually a republish of code you do not own, where
the version in the source pubspec is not the one you want to occupy on
your server. So --from-git asks:
Version ───────────────────────────────────────────────────── detected: 0.0.8-pr2 (PR #2) Publish as [0.0.8-pr2]:Press Enter to take the detected version. Type anything else and that
becomes the published version, used verbatim: entering 2.0.0 while
publishing PR #2 publishes 2.0.0, not 2.0.0-pr2. The suffix is the
default, not a rule imposed on a version you chose yourself.
The value is checked as semver before anything is published, and a typo just re-asks rather than losing the run:
Publish as [0.0.8-pr2]: v2.0 Not valid semver: "v2.0". Try 1.2.3, 1.2.3-beta.1, or 1.2.3+build5. Publish as [0.0.8-pr2]:The step is skipped, and the detected version used, whenever there is nobody to ask or the answer is already known:
| Situation | Why |
|---|---|
--version was passed | The answer is already given. |
--force | Means “stop asking me things”, the same as for every other prompt. |
| CI, or a non-interactive shell | Nobody to ask. |
--version is the non-interactive equivalent, and is validated the same
way before any cloning happens:
club publish --from-git https://github.com/your-org/your-package/pull/2 \ --version 2.0.0With --auto
In a monorepo there is no single detected version to offer, so the step asks whether to publish the whole stack as one version:
Version ───────────────────────────────────────────────────── every package publishes with its own version suffixed for PR #7, e.g. 1.2.0-pr7 enter a version to publish the whole stack as that version instead Publish all packages as:Enter nothing and each package keeps its own version. Enter 2.0.0 and
every package in the closure publishes as 2.0.0, with the rewritten
internal constraints pointing at it:
dependencies: pkg_b: hosted: https://myclub.birju.dev version: ^2.0.0Reuse and the clone cache
~/.club/clones/<host>/<org>/<repo> is treated as a managed cache,
not a persistent checkout. Two rules govern it:
| Outcome | What happens to the clone |
|---|---|
| Publish succeeded | Clone is deleted. Empty parent dirs under ~/.club/clones/ are pruned. |
| Publish failed | Clone is kept so the next run can hard-reset and re-checkout instead of cloning from scratch. |
A re-run after failure is foolproof: regardless of what was committed,
modified, or left behind in the working tree, Club fetches the latest
refs, force-checks-out the requested ref (resetting any local
divergence), and runs git clean -ffdx to wipe untracked and ignored
files. There is no way for stale state to leak into a publish.
The reuse path is also safe across different repos: each one has its own namespaced directory, and a directory pointing at a different remote is wiped and re-cloned automatically.
Cleaning the cache manually
The cache is just a directory. Safe to delete by hand at any time:
rm -rf ~/.club/clonesThe next --from-git run will recreate any directories it needs.
URL formats
| Form | Example |
|---|---|
| HTTPS | https://github.com/your-org/your-package |
HTTPS with .git | https://github.com/your-org/your-package.git |
| SSH (SCP-style) | git@github.com:your-org/your-package.git |
| SSH (URL-style) | ssh://git@github.com/your-org/your-package.git |
| Nested groups | https://gitlab.com/group/subgroup/repo.git |
| GitHub pull request | https://github.com/your-org/your-package/pull/2 |
A pull request URL is normalised to the plain repository URL before cloning, so PR and branch publishes of the same repo share one cache directory. See Pull requests.
Nested groups (GitLab subgroups, Gitea organisations with sub-orgs, etc.) map naturally onto nested cache directories:
~/.club/clones/gitlab.com/group/subgroup/repoPrivate repositories
Authentication is delegated to your local git setup. Whatever lets
git clone succeed in a regular terminal (SSH key in ssh-agent, an
HTTPS credential helper, an OAuth token, a ~/.netrc entry) is what
Club uses. No --from-git-specific auth flags exist.
Flags
--from-git accepts every standard club publish flag, with two
additions and one restriction.
| Flag | Description |
|---|---|
--from-git <url> | Git URL to clone and publish from. HTTPS, SSH (SCP-style or URL-style), and GitHub pull request URLs are all accepted. |
--ref <ref> | Branch, tag, or full commit SHA to check out. Defaults to the remote default branch. Only valid with --from-git, and not with a PR URL. |
--directory <path>, -C | When --from-git is set, resolved relative to the clone root. Useful for publishing a single package in a monorepo without --auto. |
--auto | Run multi-package discovery + topological publish over the clone. See club publish --auto. |
--dry-run, -n | Clone (or reuse), validate, and bail before uploading. The clone is still removed on success. |
--force, -f | Skip confirmation prompts (including the version step), force-publish existing versions. Required to re-publish an updated PR. |
--version <semver> | Publish as this version, skipping the version step. Applies to every package under --auto. Validated before cloning. |
--ref without --from-git | Rejected with a config error. |
--ref with a PR URL | Rejected with a config error. The PR URL already selects the commit. |
--from-archive | Not compatible with --from-git (the archive is already built; there is nothing to clone). |
All other club publish flags (--server, --enhanced,
--skip-validation, --to-archive, the --auto flags
--on-conflict, --tree, --no-tree) behave exactly as in the
standard flow.
Examples
Publish exactly what was released as v1.4.0 upstream:
club publish --from-git https://github.com/upstream/cool_package \ --ref v1.4.0Publish the current tip of a feature branch in a fork:
club publish --from-git https://github.com/your-fork/cool_package \ --ref my-patch-branchPublish a specific commit. The SHA has to be the full 40 characters, because abbreviated SHAs cannot be fetched from a remote:
club publish --from-git https://github.com/upstream/cool_package \ --ref a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0Publish the head of PR #2 as a prerelease (1.2.0 becomes
1.2.0-pr2):
club publish --from-git https://github.com/upstream/cool_package/pull/2Re-publish after the author pushes new commits:
club publish --from-git https://github.com/upstream/cool_package/pull/2 \ --forceSee Pull requests for the versioning rules.
Publish every package in a Dart workspace monorepo:
club publish --from-git https://github.com/your-org/your-monorepo \ --auto \ --on-conflict skipCombines with all the --auto flags:
--on-conflict, --tree, --no-tree, positional package names.
Publish a single subpackage of a monorepo without invoking --auto:
club publish --from-git https://github.com/your-org/your-monorepo \ -C packages/corePin which club server the publish targets, regardless of stored
credentials or any publish_to: in the pubspec:
club publish --from-git https://github.com/your-org/your-package \ --server myclub.birju.devCI usage
--from-git is CI-friendly. The non-TTY rules from
club publish and club publish --auto
apply unchanged. The one extra consideration is git auth: the CI runner
needs to be able to git clone the URL you pass.
- name: Mirror upstream release to private club server run: | club publish --from-git https://github.com/upstream/cool_package \ --ref ${{ github.event.release.tag_name }} \ --server myclub.birju.dev \ --on-conflict abort env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}For private source repos, configure your CI’s standard git credential
mechanism (SSH key, deploy token, OAuth token), then --from-git works
with no additional flags.
The cache lives under $HOME on the runner. If your CI persists
$HOME across job runs, expect the fast reuse path to kick in on
subsequent invocations of the same repo.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. Clone was removed. |
65 | Data error from the publish flow (validation failed, version conflict without --force, etc.). Clone is kept. |
66 | No input (e.g. missing pubspec.yaml at the resolved directory). Clone is kept. |
69 | Git failure (git not on PATH, unknown ref, unknown pull request, network error, repo not accessible). Clone is kept if it was created. |
78 | Config error (--ref without --from-git, --ref or --version with a PR URL, --from-git combined with --from-archive, a non-GitHub pull request URL, etc.). No clone happens. |
When the clone is kept, the CLI prints the cache path and a hint that a re-run will reuse it.
Limitations
- Authentication for the source repo is the user’s responsibility.
Whatever lets
git clone <url>succeed is what makes--from-gitsucceed. Club does not prompt for git credentials. - Git submodules are not initialised. If the package being
published depends on submodule content at build time, run
git clone --recurse-submodulesinto a regular checkout and use the plainclub publishflow instead. - Commit refs must be full 40-char SHAs. When
--refis a commit (rather than a branch or tag), pass the complete SHA. Abbreviated SHAs are rejected by every git remote and cannot be fetched shallowly. Branch and tag names accept their usual form. - Commit refs need server-side SHA fetch support. Fetching a
specific commit by SHA requires the remote’s
uploadpack.allowAnySHA1InWantto be enabled. GitHub, GitLab, and Gitea enable this by default; some self-hosted servers may need a config change. Branch/tag refs work everywhere. - Pull request URLs are GitHub-only. Other forges expose their own
PR refs, so they are still reachable through
--ref(for example--ref refs/merge-requests/4/headon GitLab), but without the automatic-pr<n>version. See Pull requests. - A PR is published from its head, not its merge result. Club
fetches
refs/pull/<n>/head, which is what the author pushed. It does not use GitHub’s synthesized merge commit, which does not exist when the PR has conflicts and would otherwise make the published contents depend on the state of the base branch.