Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

320 linesCodeBlameRaw
1---
2title: Maven
3description: 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.
4---
5
6Every workspace has a Maven repository of its own, in the standard
7layout that Maven, Gradle and every other JVM build tool read. `mvn
8deploy` and Gradle's `publish` upload to it, and builds resolve
9dependencies from it, private ones included.
10
11```text
12https://g1t.sh/-/maven/<workspace>/
13```
14
15Dependencies from Maven Central still come from Maven Central; only the
16artifacts you deploy here come from g1t.
17
18## Credentials
19
20Maven and Gradle send Basic credentials: any username, and an
21[access token](https://g1t.sh/settings/tokens) as the password. A token
22with full access works; one with scopes needs `packages:read` to resolve
23private artifacts and `packages:write` to deploy. Public artifacts resolve
24without one.
25
26For Maven, put the credentials in `~/.m2/settings.xml`, as a `<server>`
27whose `id` is the one you give the repository in `pom.xml`. These pages use
28the workspace's slug:
29
30```xml
31<settings>
32 <servers>
33 <server>
34 <id>acme</id>
35 <username>ada</username>
36 <password>${env.G1T_TOKEN}</password>
37 </server>
38 </servers>
39</settings>
40```
41
42Maven reads `${env.G1T_TOKEN}` from the environment, so the token stays
43out of the file.
44
45For Gradle, keep them in `~/.gradle/gradle.properties`, named for the
46repository (`acmeUsername` and `acmePassword` for a repository named
47`acme`), or read them from the environment as the examples below do.
48
49## Deploy with Maven
50
51Name the repository in `pom.xml`, for releases and SNAPSHOTs alike, and say
52which repository the source is in:
53
54```xml
55<project>
56 <groupId>com.acme</groupId>
57 <artifactId>http-client</artifactId>
58 <version>0.3.1</version>
59
60 <scm>
61 <url>https://g1t.sh/acme/http-client</url>
62 </scm>
63
64 <distributionManagement>
65 <repository>
66 <id>acme</id>
67 <url>https://g1t.sh/-/maven/acme/</url>
68 </repository>
69 <snapshotRepository>
70 <id>acme</id>
71 <url>https://g1t.sh/-/maven/acme/</url>
72 </snapshotRepository>
73 </distributionManagement>
74</project>
75```
76
77```sh
78mvn deploy
79```
80
81Maven uploads the artifact, its POM and their checksums, then the
82artifact's `maven-metadata.xml`. The artifact is named by its coordinates,
83`com.acme:http-client`, and its page on g1t.sh shows the description from
84the POM of its highest release.
85
86## Deploy with Gradle
87
88With the `maven-publish` plugin, in `build.gradle.kts`:
89
90```kotlin
91plugins {
92 `java-library`
93 `maven-publish`
94}
95
96group = "com.acme"
97version = "0.3.1"
98
99publishing {
100 publications {
101 create<MavenPublication>("library") {
102 from(components["java"])
103 pom {
104 scm { url = "https://g1t.sh/acme/http-client" }
105 }
106 }
107 }
108 repositories {
109 maven {
110 name = "acme"
111 url = uri("https://g1t.sh/-/maven/acme/")
112 credentials(PasswordCredentials::class)
113 }
114 }
115}
116```
117
118```sh
119./gradlew publish
120```
121
122`credentials(PasswordCredentials::class)` reads `acmeUsername` and
123`acmePassword` from `gradle.properties` or from the environment as
124`ORG_GRADLE_PROJECT_acmeUsername` and `ORG_GRADLE_PROJECT_acmePassword`.
125
126Gradle uploads the jar, the POM, the sources and javadoc jars if you build
127them, and its Gradle Module Metadata (`.module`), each with its `.md5`,
128`.sha1`, `.sha256` and `.sha512`. The `.module` file is served beside the
129POM, so a Gradle build that depends on the artifact reads its variants
130(API and runtime dependencies, capabilities) from it, and Maven reads the
131POM. Gradle's `HEAD` requests, which it makes to check files it has
132cached (with `--refresh-dependencies`, or for a SNAPSHOT), are answered
133with each file's size and type.
134
135## Which repository an artifact belongs to
136
137The first file deployed makes the artifact. When a repository of the
138workspace is named like its artifactId (`http-client`), it is linked to
139that repository and has its visibility and roles: deploying needs Write
140on it. Otherwise, the POM's `<scm><url>` (or its `<url>`) naming a g1t.sh
141repository of the workspace links a new artifact to that repository, if you
142may write to it. An artifact linked to neither is the workspace's, private,
143and needs the workspace's Write base permission. See
144[who can see and publish a package](/guides/packages/#who-can-see-and-publish-a-package).
145
146## Depend on an artifact
147
148In `pom.xml`, name the repository, with the same `id` as the `<server>`
149holding your credentials:
150
151```xml
152<repositories>
153 <repository>
154 <id>acme</id>
155 <url>https://g1t.sh/-/maven/acme/</url>
156 </repository>
157</repositories>
158
159<dependencies>
160 <dependency>
161 <groupId>com.acme</groupId>
162 <artifactId>http-client</artifactId>
163 <version>0.3.1</version>
164 </dependency>
165</dependencies>
166```
167
168In Gradle:
169
170```kotlin
171repositories {
172 mavenCentral()
173 maven {
174 name = "acme"
175 url = uri("https://g1t.sh/-/maven/acme/")
176 credentials(PasswordCredentials::class)
177 }
178}
179
180dependencies {
181 implementation("com.acme:http-client:0.3.1")
182}
183```
184
185To fetch one artifact without a project:
186
187```sh
188mvn dependency:get -Dartifact=com.acme:http-client:0.3.1 \
189 -DremoteRepositories=acme::default::https://g1t.sh/-/maven/acme/
190```
191
192## Maven plugins
193
194A plugin is deployed like any other artifact, with `<packaging>maven-plugin</packaging>`.
195g1t lists the plugins of each group in the group's own
196`maven-metadata.xml` (`com/acme/maven-metadata.xml` for the group
197`com.acme`), with the prefix each is called by: the `goalPrefix` from the
198descriptor `maven-plugin-plugin` puts in its jar, else the one Maven
199works out from its artifactId (`hello-maven-plugin` is `hello`).
200
201To call a plugin by its prefix, as `mvn hello:greet`, name its group in
202`~/.m2/settings.xml` and the repository as a plugin repository:
203
204```xml
205<settings>
206 <pluginGroups>
207 <pluginGroup>com.acme</pluginGroup>
208 </pluginGroups>
209 <profiles>
210 <profile>
211 <id>acme</id>
212 <pluginRepositories>
213 <pluginRepository>
214 <id>acme</id>
215 <url>https://g1t.sh/-/maven/acme/</url>
216 </pluginRepository>
217 </pluginRepositories>
218 </profile>
219 </profiles>
220 <activeProfiles>
221 <activeProfile>acme</activeProfile>
222 </activeProfiles>
223</settings>
224```
225
226The group's metadata lists only the plugins the credentials' owner may
227see. A plugin's full coordinates (`mvn com.acme:hello-maven-plugin:1.0.0:greet`)
228work without the plugin group.
229
230## SNAPSHOTs
231
232A version ending in `-SNAPSHOT` takes a new build each time it is
233deployed. Maven and Gradle upload each build's files with a timestamp and
234build number in their names (`http-client-0.4.0-20261006.120000-3.jar`),
235and the version's `maven-metadata.xml` names the newest build of each file,
236which is what a build depending on `0.4.0-SNAPSHOT` resolves. Earlier
237builds stay, by their full names.
238
239## Versions and files
240
241- **A release's files are written once.** Deploying a file of a released
242 version again with different content is refused with `409`; the same
243 content again is accepted, so a deploy that stopped part way can be run
244 again. Bump the version to change a release.
245- **Checksums are worked out by g1t.** The `.md5`, `.sha1`, `.sha256` and
246 `.sha512` beside every file are answered from the file itself. Checksums
247 uploaded are checked against it, and a mismatch is refused with `400`.
248- **`maven-metadata.xml` is made by g1t** from the versions there, so it
249 always lists every version, with the highest as `latest` and the highest
250 that is not a SNAPSHOT as `release`, and a group's lists its
251 [plugins](#maven-plugins). The one a build uploads is accepted and not
252 kept.
253- **The deploy's last step publishes it.** Maven and Gradle upload the
254 artifact's `maven-metadata.xml` after its files. Then each version (or
255 SNAPSHOT build) whose POM arrived in the deploy is published: it is an
256 audit entry and a `package.published` event, with every file in place.
257 A POM whose `groupId`, `artifactId` or `version` is not the one in its
258 path is refused with `400`.
259
260Delete a version, or the artifact, on its page on g1t.sh, with Admin on
261the linked repository (an owner, for the workspace's own artifacts).
262
263## Private and public artifacts
264
265An artifact linked to a repository has the repository's visibility; one of
266the workspace's own is private until an owner makes it public on its page.
267
268| The workspace's artifacts | Without credentials | With credentials |
269| --- | --- | --- |
270| All public | Maven and Gradle download them. | The same; credentials are sent to deploy. |
271| Some private | Every request without credentials is answered `401`, which makes Maven and Gradle send theirs. | Each artifact the credentials' owner may see. |
272
273A private artifact you cannot see looks exactly like one that does not
274exist.
275
276## In workflows
277
278A workflow's `G1T_TOKEN`, [the job's own token](/guides/actions/#the-jobs-token), can resolve the workspace's artifacts,
279and deploy them with `packages: write` in its [`permissions:`](/guides/actions/#the-jobs-token). With the `settings.xml`
280above, give it to Maven as `G1T_TOKEN`:
281
282```yaml
283jobs:
284 deploy:
285 runs-on: ubuntu-latest
286 permissions:
287 contents: read
288 packages: write
289 env:
290 G1T_TOKEN: ${{ secrets.G1T_TOKEN }}
291 steps:
292 - uses: actions/checkout@v4
293 - run: |
294 mkdir -p ~/.m2
295 printf '<settings><servers><server><id>acme</id><username>g1t</username><password>${env.G1T_TOKEN}</password></server></servers></settings>\n' > ~/.m2/settings.xml
296 - run: mvn --batch-mode deploy
297```
298
299For Gradle, set `ORG_GRADLE_PROJECT_acmeUsername: g1t` and
300`ORG_GRADLE_PROJECT_acmePassword: ${{ secrets.G1T_TOKEN }}` and run
301`./gradlew publish`.
302
303## Size
304
305Each file is one request and may be at most 100 MB. Without the
306[g1t plan](/guides/usage-and-billing/#the-g1t-plan), a workspace's private
307packages may hold 500 MB and its public ones 10 GB, as for
308[container images](/guides/containers/#storage-and-pull-limits). A file is
309stored once, by its content.
310
311## Errors
312
313| Error | Means |
314| --- | --- |
315| `401` | No credentials, or a wrong or expired token. Check the `<server>` whose `id` matches the repository's, or Gradle's `acmeUsername` and `acmePassword`. |
316| `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. |
317| `404` | No such artifact, version or file, or a private one you cannot see. |
318| `409` | That file of a release is already there with other content. Bump the version. |
319| `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. |
320| `413` | The file is over 100 MB. |