Skip to content

Deploy and depend on a workspace's Java and Kotlin libraries with Maven or Gradle, from a Maven repository of its own on g1t.sh, SNAPSHOTs included.

Every workspace has a Maven repository of its own, in the standard layout that Maven, Gradle and every other JVM build tool read. mvn deploy and Gradle’s publish upload to it, and builds resolve dependencies from it, private ones included.

https://g1t.sh/-/maven/<workspace>/

Dependencies from Maven Central still come from Maven Central; only the artifacts you deploy here come from g1t.

Maven and Gradle send Basic credentials: any username, and an access token as the password. A token with full access works; one with scopes needs packages:read to resolve private artifacts and packages:write to deploy. Public artifacts resolve without one.

For Maven, put the credentials in ~/.m2/settings.xml, as a <server> whose id is the one you give the repository in pom.xml. These pages use the workspace’s slug:

<settings>
<servers>
<server>
<id>acme</id>
<username>ada</username>
<password>${env.G1T_TOKEN}</password>
</server>
</servers>
</settings>

Maven reads ${env.G1T_TOKEN} from the environment, so the token stays out of the file.

For Gradle, keep them in ~/.gradle/gradle.properties, named for the repository (acmeUsername and acmePassword for a repository named acme), or read them from the environment as the examples below do.

Name the repository in pom.xml, for releases and SNAPSHOTs alike, and say which repository the source is in:

<project>
<groupId>com.acme</groupId>
<artifactId>http-client</artifactId>
<version>0.3.1</version>
<scm>
<url>https://g1t.sh/acme/http-client</url>
</scm>
<distributionManagement>
<repository>
<id>acme</id>
<url>https://g1t.sh/-/maven/acme/</url>
</repository>
<snapshotRepository>
<id>acme</id>
<url>https://g1t.sh/-/maven/acme/</url>
</snapshotRepository>
</distributionManagement>
</project>
mvn deploy

Maven uploads the artifact, its POM and their checksums, then the artifact’s maven-metadata.xml. The artifact is named by its coordinates, com.acme:http-client, and its page on g1t.sh shows the description from the POM of its highest release.

With the maven-publish plugin, in build.gradle.kts:

plugins {
`java-library`
`maven-publish`
}
group = "com.acme"
version = "0.3.1"
publishing {
publications {
create<MavenPublication>("library") {
from(components["java"])
pom {
scm { url = "https://g1t.sh/acme/http-client" }
}
}
}
repositories {
maven {
name = "acme"
url = uri("https://g1t.sh/-/maven/acme/")
credentials(PasswordCredentials::class)
}
}
}
./gradlew publish

credentials(PasswordCredentials::class) reads acmeUsername and acmePassword from gradle.properties or from the environment as ORG_GRADLE_PROJECT_acmeUsername and ORG_GRADLE_PROJECT_acmePassword.

Gradle uploads the jar, the POM, the sources and javadoc jars if you build them, and its Gradle Module Metadata (.module), each with its .md5, .sha1, .sha256 and .sha512. The .module file is served beside the POM, so a Gradle build that depends on the artifact reads its variants (API and runtime dependencies, capabilities) from it, and Maven reads the POM. Gradle’s HEAD requests, which it makes to check files it has cached (with --refresh-dependencies, or for a SNAPSHOT), are answered with each file’s size and type.

The first file deployed makes the artifact. When a repository of the workspace is named like its artifactId (http-client), it is linked to that repository and has its visibility and roles: deploying needs Write on it. Otherwise, the POM’s <scm><url> (or its <url>) naming a g1t.sh repository of the workspace links a new artifact to that repository, if you may write to it. An artifact linked to neither is the workspace’s, private, and needs the workspace’s Write base permission. See who can see and publish a package.

In pom.xml, name the repository, with the same id as the <server> holding your credentials:

<repositories>
<repository>
<id>acme</id>
<url>https://g1t.sh/-/maven/acme/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.acme</groupId>
<artifactId>http-client</artifactId>
<version>0.3.1</version>
</dependency>
</dependencies>

In Gradle:

repositories {
mavenCentral()
maven {
name = "acme"
url = uri("https://g1t.sh/-/maven/acme/")
credentials(PasswordCredentials::class)
}
}
dependencies {
implementation("com.acme:http-client:0.3.1")
}

To fetch one artifact without a project:

mvn dependency:get -Dartifact=com.acme:http-client:0.3.1 \
-DremoteRepositories=acme::default::https://g1t.sh/-/maven/acme/

A plugin is deployed like any other artifact, with <packaging>maven-plugin</packaging>. g1t lists the plugins of each group in the group’s own maven-metadata.xml (com/acme/maven-metadata.xml for the group com.acme), with the prefix each is called by: the goalPrefix from the descriptor maven-plugin-plugin puts in its jar, else the one Maven works out from its artifactId (hello-maven-plugin is hello).

To call a plugin by its prefix, as mvn hello:greet, name its group in ~/.m2/settings.xml and the repository as a plugin repository:

<settings>
<pluginGroups>
<pluginGroup>com.acme</pluginGroup>
</pluginGroups>
<profiles>
<profile>
<id>acme</id>
<pluginRepositories>
<pluginRepository>
<id>acme</id>
<url>https://g1t.sh/-/maven/acme/</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>acme</activeProfile>
</activeProfiles>
</settings>

The group’s metadata lists only the plugins the credentials’ owner may see. A plugin’s full coordinates (mvn com.acme:hello-maven-plugin:1.0.0:greet) work without the plugin group.

A version ending in -SNAPSHOT takes a new build each time it is deployed. Maven and Gradle upload each build’s files with a timestamp and build number in their names (http-client-0.4.0-20261006.120000-3.jar), and the version’s maven-metadata.xml names the newest build of each file, which is what a build depending on 0.4.0-SNAPSHOT resolves. Earlier builds stay, by their full names.

  • A release’s files are written once. Deploying a file of a released version again with different content is refused with 409; the same content again is accepted, so a deploy that stopped part way can be run again. Bump the version to change a release.
  • Checksums are worked out by g1t. The .md5, .sha1, .sha256 and .sha512 beside every file are answered from the file itself. Checksums uploaded are checked against it, and a mismatch is refused with 400.
  • maven-metadata.xml is made by g1t from the versions there, so it always lists every version, with the highest as latest and the highest that is not a SNAPSHOT as release, and a group’s lists its plugins. The one a build uploads is accepted and not kept.
  • The deploy’s last step publishes it. Maven and Gradle upload the artifact’s maven-metadata.xml after its files. Then each version (or SNAPSHOT build) whose POM arrived in the deploy is published: it is an audit entry and a package.published event, with every file in place. A POM whose groupId, artifactId or version is not the one in its path is refused with 400.

Delete a version, or the artifact, on its page on g1t.sh, with Admin on the linked repository (an owner, for the workspace’s own artifacts).

An artifact linked to a repository has the repository’s visibility; one of the workspace’s own is private until an owner makes it public on its page.

The workspace’s artifacts Without credentials With credentials
All public Maven and Gradle download them. The same; credentials are sent to deploy.
Some private Every request without credentials is answered 401, which makes Maven and Gradle send theirs. Each artifact the credentials’ owner may see.

A private artifact you cannot see looks exactly like one that does not exist.

A workflow’s G1T_TOKEN is the workspace’s own token for the run, and can resolve and deploy the workspace’s artifacts. With the settings.xml above, give it to Maven as G1T_TOKEN:

jobs:
deploy:
runs-on: ubuntu-latest
env:
G1T_TOKEN: ${{ secrets.G1T_TOKEN }}
steps:
- uses: actions/checkout@v4
- run: |
mkdir -p ~/.m2
printf '<settings><servers><server><id>acme</id><username>g1t</username><password>${env.G1T_TOKEN}</password></server></servers></settings>\n' > ~/.m2/settings.xml
- run: mvn --batch-mode deploy

For Gradle, set ORG_GRADLE_PROJECT_acmeUsername: g1t and ORG_GRADLE_PROJECT_acmePassword: ${{ secrets.G1T_TOKEN }} and run ./gradlew publish.

Each file is one request and may be at most 100 MB. Without the g1t plan, a workspace’s private packages may hold 500 MB and its public ones 10 GB, as for container images. A file is stored once, by its content.

Error Means
401 No credentials, or a wrong or expired token. Check the <server> whose id matches the repository’s, or Gradle’s acmeUsername and acmePassword.
403 Signed in, but your role or your token’s scopes do not allow it, or the workspace is out of free package storage. The response says which.
404 No such artifact, version or file, or a private one you cannot see.
409 That file of a release is already there with other content. Bump the version.
400 The upload was refused: a POM that does not match its path, or a checksum that does not match its file. The response says which.
413 The file is over 100 MB.