The easiest way to get the right toolchain versions is mise:
mise install # installs Java 21 (Temurin) and Node 24 as defined in mise.tomlIf you prefer to manage tools yourself, you need:
- Java 21+
- Node 24+
- Docker or Podman (for e2e tests and Docker Compose workflows)
Gradle itself is included via the wrapper — no separate installation needed.
./gradlew spotlessApply # fix formatting (palantir-java-format) — run before build
./gradlew build # compile + unit testsFormatting is enforced in CI. Always run spotlessApply before pushing.
When working on Java-only changes in the dashboard module, pass -PskipFrontend to skip the Node/npm frontend build
steps (requires Node to be available otherwise):
./gradlew :git-proxy-java-dashboard:compileJava -PskipFrontend
./gradlew :git-proxy-java-dashboard:build -PskipFrontend./gradlew :git-proxy-java-server:runListens on http://localhost:8080. Logs go to git-proxy-java-server/logs/application.log. Stop with:
./gradlew :git-proxy-java-server:stop./gradlew :git-proxy-java-dashboard:runOpens the approval dashboard at http://localhost:8080/. Stop with:
./gradlew :git-proxy-java-dashboard:stopThe dashboard module always uses UI-mode approval (pushes block until manually approved). The standalone server defaults to auto-approve.
Place overrides in git-proxy-java-server/src/main/resources/git-proxy-local.yml. The local file takes priority over
git-proxy.yml. At minimum, add an allow rule for your test repo and a permission entry for your proxy user:
rules:
allow:
- enabled: true
order: 110
operations: [FETCH, PUSH]
providers: [github]
slugs:
- /your-org/your-repo
permissions:
- username: your-proxy-user
provider: github
path: /your-org/your-repo
operations: PUSHSee docs/CONFIGURATION.md for the full reference.
./gradlew testUnit tests live under each module's src/test/. They run without containers.
./gradlew e2eTestThese start a containerised Gitea instance and a live Jetty proxy in-process. They are tagged @Tag("e2e") and live in
git-proxy-java-server/src/test/java/org/finos/gitproxy/e2e/.
The test/ directory contains bash scripts for exercising both proxy modes against a running server. They are the
fastest way to verify a feature end-to-end without writing Java.
Test scripts share a common library (test/common.sh) with setup, cleanup, and assertion helpers. Individual test cases
are organized into logical groupings by test outcome (pass/fail) and proxy mode (push/proxy).
All scripts share these variables:
| Variable | Default | Description |
|---|---|---|
GIT_USERNAME |
me |
HTTP Basic-auth username (arbitrary for the proxy) |
GIT_PASSWORD |
(read from PAT file, see below) | Personal access token for the upstream SCM |
GIT_REPO |
github.com/coopernetes/test-repo.git |
Target repo for GitHub pass/fail scripts |
GITHUB_REPO |
github.com/coopernetes/test-repo.git |
Target repo for GitHub identity scripts |
GITLAB_REPO |
gitlab.com/coopernetes/test-repo-gitlab.git |
Target repo for GitLab identity scripts |
CODEBERG_REPO |
codeberg.org/coopernetes/test-repo-codeberg.git |
Target repo for Codeberg identity scripts |
GITPROXY_API_KEY |
change-me-in-production |
API key used by approval scripts |
Scripts read the upstream PAT from a file if GIT_PASSWORD is not set:
| Script group | PAT file |
|---|---|
| GitHub scripts | ~/.github-pat |
| GitLab scripts | ~/.gitlab-pat |
| Codeberg scripts | ~/.codeberg-pat |
Run tests by logical grouping. Each entry point orchestrates multiple related test cases:
Store-and-forward (push):
bash test/push-pass-all.sh— golden-path pushes and tag pushes (should succeed)bash test/push-fail-all.sh— validation failures (should be rejected)
Transparent proxy:
bash test/proxy-pass-all.sh— golden-path and tag pushes (require manual approval)bash test/proxy-fail-all.sh— validation failures (should be rejected)
Identity verification:
bash test/push-identity-all.sh— SCM identity resolution across providersbash test/proxy-identity-all.sh— SCM identity resolution via proxy
If running a single test case by name:
Store-and-forward (push): | Script | Category | What it tests | | ---------------------- | -------- |
------------------------------------------------------ | | push-pass.sh | Pass | Golden-path push — should succeed and
forward upstream | | push-pass-tag.sh | Pass | Lightweight and annotated tags — should succeed | |
push-pass-secrets.sh | Pass | File patterns that look like secrets but pass gitleaks | | push-fail-author.sh | Fail
| Invalid author email domains (noreply, disallowed) | | push-fail-message.sh | Fail | Commit message validation (WIP,
fixup, DO NOT MERGE) | | push-fail-diff.sh | Fail | Diff content scanning (internal URLs, patterns) | |
push-fail-secrets.sh | Fail | Gitleaks detecting secrets in diff (AWS, GitHub, PEM) |
Transparent proxy: | Script | Category | What it tests | | ----------------------- | -------- |
---------------------------------------------------------------- | | proxy-pass.sh | Pass | Golden-path push — blocks
for approval, then auto-approves | | proxy-pass-tag.sh | Pass | Lightweight and annotated tags through proxy | |
proxy-fail-author.sh | Fail | Invalid author email domains (noreply, disallowed) | | proxy-fail-message.sh | Fail |
Commit message validation (WIP, fixup, DO NOT MERGE) | | proxy-fail-diff.sh | Fail | Diff content scanning (internal
URLs, patterns) | | proxy-fail-secrets.sh | Fail | Gitleaks detecting secrets in diff (AWS, GitHub, PEM) |
The scripts default to repos owned by the project maintainer. To run them against your own repos you need:
-
A test repo you can push to on GitHub (and optionally GitLab/Codeberg for identity tests).
-
PAT files for each provider you want to test:
echo "ghp_yourtoken" > ~/.github-pat echo "glpat-yourtoken" > ~/.gitlab-pat # optional echo "yourtoken" > ~/.codeberg-pat # optional chmod 600 ~/.github-pat ~/.gitlab-pat ~/.codeberg-pat
-
Allow rules and permissions in
git-proxy-local.yml— add your repo slug to therules.allowslugs list and addPUSH/REVIEWpermission entries for your proxy user. See docs/CONFIGURATION.md for the full reference. -
Run with your repo — single scripts accept an inline override; orchestrators need an export:
# Single script — inline is fine: GIT_REPO=github.com/your-org/your-repo.git bash test/push-pass.sh # Orchestrators (call subscripts via bash) — must export: export GIT_REPO=github.com/your-org/your-repo.git bash test/push-pass-all.sh bash test/proxy-pass-all.sh # Provider-specific identity tests use separate variables: export GITHUB_REPO=github.com/your-org/your-repo.git export GITLAB_REPO=gitlab.com/your-org/your-repo.git bash test/push-identity-all.sh
Make sure the server is running first (see above), then:
# Run all passing push tests:
bash test/push-pass-all.sh
# Run all failure push tests:
bash test/push-fail-all.sh
# Run a single test case:
bash test/push-fail-secrets.shTwo scripts spin up a complete Docker Compose environment (git-proxy-java + Gitea + database), run all test groups, then tear down:
bash test/run-postgres.sh # PostgreSQL backend
bash test/run-mongo.sh # MongoDB backend
# Leave the environment running after the suite (useful for debugging):
bash test/run-postgres.sh --no-teardownThese build the Docker image from source, so no pre-existing server is needed.
The Compose setup runs git-proxy-java against a local Gitea instance. Overlay files are independent mixins — one for the auth provider, one for the database backend. They can be combined freely.
Auth overlays — each mounts a different git-proxy-local.yml config into the container:
| File | Auth provider | Default database |
|---|---|---|
| (none) | Static (password hashes in config) | H2 in-memory |
docker-compose.ldap.yml |
OpenLDAP | H2 in-memory |
docker-compose.oidc.yml |
OIDC (mock-oauth2-server) | H2 in-memory |
Database overlays — each sets GITPROXY_DATABASE_* environment variables; no config file swap needed:
| File | Backend | Profile flag | UI |
|---|---|---|---|
| (none) | H2 in-memory | — | — |
docker-compose.postgres.yml |
PostgreSQL | --profile postgres |
Adminer at :8082 |
docker-compose.mongo.yml |
MongoDB | --profile mongo |
Mongo Express at :8081 |
Any auth overlay can be combined with any database overlay (or none, to keep H2). The pattern is:
docker compose [--profile <db>] \
-f docker-compose.yml \
[-f docker-compose.<auth>.yml] \
[-f docker-compose.<db>.yml] \
up -dAfter starting any stack, run this once to create the Gitea admin user and test repository:
bash docker/gitea-setup.shStatic auth + H2 (simplest — no external dependencies):
docker compose up -dLDAP + H2:
docker compose -f docker-compose.yml -f docker-compose.ldap.yml up -dLDAP + PostgreSQL (recommended for verifying IdP email locking and auto-provisioning):
docker compose --profile postgres \
-f docker-compose.yml \
-f docker-compose.ldap.yml \
-f docker-compose.postgres.yml \
up -dOIDC + PostgreSQL:
docker compose --profile postgres \
-f docker-compose.yml \
-f docker-compose.oidc.yml \
-f docker-compose.postgres.yml \
up -dLDAP + MongoDB:
docker compose --profile mongo \
-f docker-compose.yml \
-f docker-compose.ldap.yml \
-f docker-compose.mongo.yml \
up -dLog in at http://localhost:8080 with admin / admin (defined in docker/git-proxy-local.yml).
Test accounts are defined in docker/ldap-bootstrap.ldif:
| Username | Password | LDAP email |
|---|---|---|
testuser |
testpass123 |
testuser@example.com |
admin |
admin |
admin@example.com |
On first login the account is auto-provisioned and the LDAP mail attribute is stored as a locked email (not editable
from the profile UI). Inspect the user_emails table in Adminer or Mongo Express to see the locked=true row.
To add more users, edit docker/ldap-bootstrap.ldif and recreate the container:
docker compose -f docker-compose.yml -f docker-compose.ldap.yml rm -sf openldap
docker compose -f docker-compose.yml -f docker-compose.ldap.yml up -d openldapUses navikt/mock-oauth2-server, which accepts any username with no password required.
One-time /etc/hosts entry — required so the OIDC issuer URL is the same from your browser and from git-proxy-java
inside Docker:
127.0.0.1 mock-oauth2
Open http://localhost:8080 and log in with any username.
After docker/gitea-setup.sh, the test repository is reachable at:
http://localhost:8080/push/gitea/test-owner/test-repo.git
http://localhost:8080/proxy/gitea/test-owner/test-repo.git
Clone example:
git clone http://gitproxyadmin:Admin1234!@localhost:8080/push/gitea/test-owner/test-repo.gitdocker compose [same -f flags as start] down -vFormatting is enforced by Spotless using palantir-java-format:
./gradlew spotlessApplyFormatting uses Prettier, lint checks use ESLint. Both are Gradle tasks that use the same Node binary as the build:
./gradlew :git-proxy-java-dashboard:npmFormat # auto-format src/ with Prettier
./gradlew :git-proxy-java-dashboard:npmLint # ESLint check (fails on errors)Install once after cloning:
./gradlew installGitHooksThis sets core.hooksPath to .githooks/. The hook runs on every git commit:
spotlessApply— auto-formats Java and re-stages changed filesnpmFormat— auto-formats frontend source with Prettier and re-stages changed filesnpmLint— ESLint check; fails the commit if there are errors (no auto-fix)
| Module | Purpose |
|---|---|
git-proxy-java-core |
Shared library: filter chain, JGit hooks, push store, provider model, approval abstraction |
git-proxy-java-server |
Standalone proxy-only server — no dashboard, no Spring |
git-proxy-java-dashboard |
Dashboard + REST API — Spring MVC, approval UI |
See docs/internals/JGIT_INFRASTRUCTURE.md for the store-and-forward architecture and docs/internals/GIT_INTERNALS.md for wire-protocol details.
Releases follow a two-phase process to ensure every published image is identical to what was already scanned and running
as :edge.
Phase 1 — version bump. Create a release/<version> branch, update version in build.gradle, open a PR, and
enable auto-merge. The PR must pass all CI, CodeQL, CVE, and container scan checks before it can merge. Use the
/release Claude command to automate this.
Phase 2 — tag. Once the version bump lands on main, push an annotated tag (v<version>). The tag ruleset enforces
the same checks must have passed on that commit. The publish workflow then promotes the already-built :edge image
directly to the release tags (:v1.0.0, :latest, etc.) — no rebuild occurs. Use the /release-tag Claude command for
this step.
This means every release image is byte-for-byte identical to the :edge image that was scanned when the version bump
merged.
The issue tracker is at coopernetes/git-proxy-java. Reference the upstream Node.js implementation at finos/git-proxy when porting features.