Merge branch 'develop' into feature/lookup_api
27
CHANGELOG.md
@@ -5,13 +5,36 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## [2.9.0] - 2020-11-06
|
||||||
|
### Added
|
||||||
|
- Tracing api ([#1393](https://github.com/scm-manager/scm-manager/pull/#1393))
|
||||||
|
- Automatic user converter for external users ([#1380](https://github.com/scm-manager/scm-manager/pull/1380))
|
||||||
|
- Create _authenticated group on setup ([#1396](https://github.com/scm-manager/scm-manager/pull/1396))
|
||||||
|
- The name of the initial git branch can be configured and is set to `main` by default ([#1399](https://github.com/scm-manager/scm-manager/pull/1399))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Internal server error for git sub modules without tree object ([#1397](https://github.com/scm-manager/scm-manager/pull/1397))
|
||||||
|
- Do not expose subversion commit with id 0 ([#1395](https://github.com/scm-manager/scm-manager/pull/1395))
|
||||||
|
- Cloning of Mercurial repositories with api keys ([#1407](https://github.com/scm-manager/scm-manager/pull/1407))
|
||||||
|
- Disable cloning repositories via ssh for anonymous users ([#1403](https://github.com/scm-manager/scm-manager/pull/1403))
|
||||||
|
- Support anonymous file download through rest api for non-browser clients (e.g. curl or postman) when anonymous mode is set to protocol-only ([#1402](https://github.com/scm-manager/scm-manager/pull/1402))
|
||||||
|
- SVN diff with property changes ([#1400](https://github.com/scm-manager/scm-manager/pull/1400))
|
||||||
|
- Branches link in repository overview ([#1404](https://github.com/scm-manager/scm-manager/pull/1404))
|
||||||
|
|
||||||
|
## [2.8.0] - 2020-10-27
|
||||||
### Added
|
### Added
|
||||||
- Generation of email addresses for users, where none is configured ([#1370](https://github.com/scm-manager/scm-manager/pull/1370))
|
- Generation of email addresses for users, where none is configured ([#1370](https://github.com/scm-manager/scm-manager/pull/1370))
|
||||||
- Source code fullscreen view ([#1376](https://github.com/scm-manager/scm-manager/pull/1376))
|
- Source code fullscreen view ([#1376](https://github.com/scm-manager/scm-manager/pull/1376))
|
||||||
|
- Plugins can now expose ui components to be shared with other plugins ([#1382](https://github.com/scm-manager/scm-manager/pull/1382))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Reduce logging of ApiTokenRealm ([#1385](https://github.com/scm-manager/scm-manager/pull/1385))
|
||||||
|
- Centralise syntax highlighting ([#1382](https://github.com/scm-manager/scm-manager/pull/1382))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Missing default permission to manage public gpg keys ([#1377](https://github.com/scm-manager/scm-manager/pull/1377))
|
- Handling of snapshot plugin dependencies ([#1384](https://github.com/scm-manager/scm-manager/pull/1384))
|
||||||
|
- SyntaxHighlighting for GoLang ([#1386](https://github.com/scm-manager/scm-manager/pull/1386))
|
||||||
|
- Privilege escalation for api keys ([#1388](https://github.com/scm-manager/scm-manager/pull/1388))
|
||||||
|
|
||||||
## [2.6.3] - 2020-10-16
|
## [2.6.3] - 2020-10-16
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
2
Jenkinsfile
vendored
@@ -204,7 +204,7 @@ String developmentBranch
|
|||||||
String mainBranch
|
String mainBranch
|
||||||
|
|
||||||
Maven setupMavenBuild() {
|
Maven setupMavenBuild() {
|
||||||
MavenWrapperInDocker mvn = new MavenWrapperInDocker(this, "scmmanager/java-build:11.0.8_10")
|
MavenWrapperInDocker mvn = new MavenWrapperInDocker(this, "scmmanager/java-build:11.0.9_11.1")
|
||||||
mvn.enableDockerHost = true
|
mvn.enableDockerHost = true
|
||||||
|
|
||||||
// disable logging durring the build
|
// disable logging durring the build
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM adoptopenjdk/openjdk11:x86_64-debian-jdk-11.0.8_10
|
FROM adoptopenjdk/openjdk11:x86_64-debian-jdk-11.0.9_11.1
|
||||||
|
|
||||||
ENV DOCKER_VERSION=19.03.8 \
|
ENV DOCKER_VERSION=19.03.8 \
|
||||||
DOCKER_CHANNEL=stable \
|
DOCKER_CHANNEL=stable \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
VERSION:=11.0.8_10
|
VERSION:=11.0.9_11.1
|
||||||
|
|
||||||
.PHONY:build
|
.PHONY:build
|
||||||
build:
|
build:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 129 KiB |
BIN
docs/de/user/admin/assets/administration-settings-git.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
23
docs/de/user/admin/git.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: Administration
|
||||||
|
subtitle: Git
|
||||||
|
---
|
||||||
|
Unter dem Eintrag Git können die folgenden Git-spezifischen Einstellungen vorgenommen werden:
|
||||||
|
|
||||||
|
- GC Cron Ausdruck
|
||||||
|
|
||||||
|
Wenn hier ein Wert gesetzt wird, führt der SCM-Manager zu den
|
||||||
|
[entsprechenden Zeiten](https://de.wikipedia.org/wiki/Cron)
|
||||||
|
eine "Git Garbage Collection" aus.
|
||||||
|
|
||||||
|
- Deaktiviere "Non Fast-Forward"
|
||||||
|
|
||||||
|
Wenn dieses aktiviert ist, werden "forcierte" Pushs abgelehnt, wenn diese keine "fast forwards" sind.
|
||||||
|
|
||||||
|
- Default Branch
|
||||||
|
|
||||||
|
Der hier gesetzte Branch Name wird bei der Initialisierung von neuen Repositories genutzt.
|
||||||
|
Bitte beachten Sie, dass dieser Name aufgrund von Git-Spezifika nicht bei leeren Repositories genutzt
|
||||||
|
werden kann (hier wird immer der Git-interne Default Name genutzt, derzeit also `master`).
|
||||||
|
|
||||||
|

|
||||||
@@ -8,6 +8,7 @@ Im Bereich Administration kann die SCM-Manager Instanz administriert werden. Von
|
|||||||
* [Plugins](plugins/)
|
* [Plugins](plugins/)
|
||||||
* [Berechtigungsrollen](roles/)
|
* [Berechtigungsrollen](roles/)
|
||||||
* [Einstellungen](settings/)
|
* [Einstellungen](settings/)
|
||||||
|
* [Git](git/)
|
||||||
<!--- AppendLinkContentEnd -->
|
<!--- AppendLinkContentEnd -->
|
||||||
|
|
||||||
### Information
|
### Information
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ Ist der anonyme Zugriff nur für Protokoll aktiviert, können die REST API und d
|
|||||||
|
|
||||||
Beispiel: Falls der anonyme Zugriff aktiviert ist und der "_anonymous"-Benutzer volle Zugriffsrechte auf ein bestimmtes Git-Repository hat, kann jeder über eine Kommandozeile mit den klassischen Git-Befehlen ohne Zugangsdaten auf dieses Repository zugreifen. Zugriffe über SSH werden aktuell nicht unterstützt.
|
Beispiel: Falls der anonyme Zugriff aktiviert ist und der "_anonymous"-Benutzer volle Zugriffsrechte auf ein bestimmtes Git-Repository hat, kann jeder über eine Kommandozeile mit den klassischen Git-Befehlen ohne Zugangsdaten auf dieses Repository zugreifen. Zugriffe über SSH werden aktuell nicht unterstützt.
|
||||||
|
|
||||||
|
#### Release Feed Url
|
||||||
|
Die URL des RSS Release Feed des SCM-Managers. Darüber wird über die neue SCM-Manager Version informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.
|
||||||
|
|
||||||
|
#### User converter
|
||||||
|
Ist der Benutzer Konverter aktiviert, werden alle internen Benutzer beim Einloggen über ein externes System automatisch zu externen Benutzern konvertiert. Nach dem Konvertieren können sich die Benutzer nicht mehr mit dem lokalen SCM-Manager Passwort einloggen, sondern nur noch über das Fremdsystem.
|
||||||
|
|
||||||
|
#### Fallback E-Mail Domain Name
|
||||||
|
Dieser Domain Name wird genutzt, wenn für einen User eine E-Mail-Adresse benötigt wird, für den keine hinterlegt ist. Diese Domain wird nicht zum Versenden von E-Mails genutzt und auch keine anderweitige Verbindung aufgebaut.
|
||||||
|
|
||||||
#### Anmeldeversuche
|
#### Anmeldeversuche
|
||||||
Es lässt sich konfigurieren wie häufig sich ein Benutzer falsch anmelden darf, bevor dessen Benutzerkonto gesperrt wird. Der Zähler für fehlerhafte Anmeldeversuche wird nach einem erfolgreichen Login zurückgesetzt. Man kann dieses Feature abschalten, indem man "-1" in die Konfiguration einträgt.
|
Es lässt sich konfigurieren wie häufig sich ein Benutzer falsch anmelden darf, bevor dessen Benutzerkonto gesperrt wird. Der Zähler für fehlerhafte Anmeldeversuche wird nach einem erfolgreichen Login zurückgesetzt. Man kann dieses Feature abschalten, indem man "-1" in die Konfiguration einträgt.
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 150 KiB |
BIN
docs/de/user/user/assets/user-create.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 64 KiB |
BIN
docs/de/user/user/assets/user-password-modal.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 45 KiB |
@@ -16,13 +16,12 @@ Auf der Benutzer Übersichtsseite wird eine Liste der existierenden Benutzer ang
|
|||||||
### Benutzer erstellen
|
### Benutzer erstellen
|
||||||
Mithilfe des "Benutzer erstellen"-Formulars können neue Benutzer im SCM-Manager angelegt werden. Neue Benutzer haben noch keine Berechtigungen und sollten direkt nach dem Anlegen konfiguriert werden.
|
Mithilfe des "Benutzer erstellen"-Formulars können neue Benutzer im SCM-Manager angelegt werden. Neue Benutzer haben noch keine Berechtigungen und sollten direkt nach dem Anlegen konfiguriert werden.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Benutzer Detailseite
|
### Benutzer Detailseite
|
||||||
Die Detailseite eines Benutzers zeigt die Informationen zu diesem an.
|
Die Detailseite eines Benutzers zeigt die Informationen zu diesem an.
|
||||||
|
|
||||||
Über den "Aktiv"-Marker sieht man, ob dies ein aktivierter Benutzer des SCM-Managers ist. Wird ein Benutzer auf inaktiv gesetzt, kann er sich nicht mehr am SCM-Manager anmelden.
|
Über den "Aktiv"-Marker sieht man, ob dies ein aktivierter Benutzer des SCM-Managers ist. Wird ein Benutzer auf inaktiv gesetzt, kann er sich nicht mehr am SCM-Manager anmelden.
|
||||||
|
Die Checkbox `Extern` zeigt an, ob es sich um einen internen Benutzer handelt oder der Benutzer von einem Fremdsystem verwaltet wird.
|
||||||
Der Typ eines Benutzers gibt an, aus welcher Quelle dieser Benutzer stammt. Der Typ "XML" aus dem Beispiel gibt an, dass dieser Benutzer im SCM-Manager erstellt wurde. Daneben kann es aber auch externe Benutzer geben, die beispielweise mithilfe des LDAP-Plugins aus einer LDAP-Instanz angebunden wurden.
|
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -3,7 +3,11 @@ title: Benutzer
|
|||||||
subtitle: Einstellungen
|
subtitle: Einstellungen
|
||||||
---
|
---
|
||||||
### Generell
|
### Generell
|
||||||
In den generellen Einstellungen des Benutzers können der Anzeigename, die E-Mail-Adresse und der Aktivitätsstatus des Kontos editiert werden.
|
In den generellen Einstellungen des Benutzers können der Anzeigename, die E-Mail-Adresse, der "Extern"-Status und der Aktivitätsstatus des Kontos editiert werden.
|
||||||
|
|
||||||
|
Wird ein interner Benutzer zu einem externen Benutzer konvertiert, wird das SCM-Manager Passwort des Benutzers entfernt. Soll ein externer Benutzer zu einem internen Benutzer umgewandelt werden, wird nach einem neuen Passwort für diesen Benutzer gefragt.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Über die Schaltfläche unten kann der Benutzer auch komplett gelöscht werden. Dieser Vorgang kann nicht rückgängig gemacht werden.
|
Über die Schaltfläche unten kann der Benutzer auch komplett gelöscht werden. Dieser Vorgang kann nicht rückgängig gemacht werden.
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 111 KiB |
BIN
docs/en/user/admin/assets/administration-settings-git.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
22
docs/en/user/admin/git.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: Administration
|
||||||
|
subtitle: Git
|
||||||
|
---
|
||||||
|
In the git section there are the following git specific settings:
|
||||||
|
|
||||||
|
- GC Cron Expression
|
||||||
|
|
||||||
|
If this is set, SCM-Manager will execute a git garbage collection matching the given
|
||||||
|
[cron expression](https://en.wikipedia.org/wiki/Cron#CRON_expression).
|
||||||
|
|
||||||
|
- Disable Non Fast-Forward
|
||||||
|
|
||||||
|
Activate this to reject forced pushes that are not fast forwards.
|
||||||
|
|
||||||
|
- Default Branch
|
||||||
|
|
||||||
|
The branch name configured here will be used for the initialization of new repositories.
|
||||||
|
Please mind, that due to git internals this cannot work for empty repositories (here git
|
||||||
|
will always use its internal default branch, so at the time being `master`).
|
||||||
|
|
||||||
|

|
||||||
@@ -7,6 +7,7 @@ The SCM-Manager instance can be administered in the Administration area. From he
|
|||||||
* [Plugins](plugins/)
|
* [Plugins](plugins/)
|
||||||
* [Permission Roles](roles/)
|
* [Permission Roles](roles/)
|
||||||
* [Settings](settings/)
|
* [Settings](settings/)
|
||||||
|
* [Git](git/)
|
||||||
|
|
||||||
### Information
|
### Information
|
||||||
On the information page in the administration area you can find the version of your SCM-Manager instance and helpful links to get in touch with the SCM-Manager support team. If there is a newer version for SCM-Manager, it will be shown with the link to the download section on the official SCM-Manager homepage.
|
On the information page in the administration area you can find the version of your SCM-Manager instance and helpful links to get in touch with the SCM-Manager support team. If there is a newer version for SCM-Manager, it will be shown with the link to the download section on the official SCM-Manager homepage.
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ If the anonymous mode is protocol only you may access the SCM-Manager via the RE
|
|||||||
|
|
||||||
Example: If anonymous access is enabled and the "_anonymous" user has full access on a certain Git repository, everybody can access this repository via command line and the classic Git commands without any login credentials. Access via SSH is not supported at this time.
|
Example: If anonymous access is enabled and the "_anonymous" user has full access on a certain Git repository, everybody can access this repository via command line and the classic Git commands without any login credentials. Access via SSH is not supported at this time.
|
||||||
|
|
||||||
|
#### Release Feed Url
|
||||||
|
The url of the RSS Release Feed for SCM-Manager. This provides up-to-date version information. To disable this feature just leave the url blank.
|
||||||
|
|
||||||
|
#### User converter
|
||||||
|
Internal users will automatically be converted to external on their first login using an external system. After conversion the users may only log in using the external system.
|
||||||
|
|
||||||
|
#### Fallback Mail Domain Name
|
||||||
|
This domain name will be used to create email addresses for users without one when needed. It will not be used to send mails nor will be accessed otherwise.
|
||||||
|
|
||||||
#### Login Attempt Limit
|
#### Login Attempt Limit
|
||||||
It can be configured how many failed login attempts a user can have before the account gets disabled. The counter for failed login attempts is reset after a successful login. This feature can be deactivated by setting the value "-1".
|
It can be configured how many failed login attempts a user can have before the account gets disabled. The counter for failed login attempts is reset after a successful login. This feature can be deactivated by setting the value "-1".
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 149 KiB |
BIN
docs/en/user/user/assets/user-create.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 64 KiB |
BIN
docs/en/user/user/assets/user-password-modal.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 46 KiB |
@@ -14,11 +14,11 @@ The user overview shows a list of all existing users. A page with details about
|
|||||||
### Create User
|
### Create User
|
||||||
The "Create User" form can be used to create new users in SCM-Manager. New users don’t have any permissions and should therefore be configured right after they were created.
|
The "Create User" form can be used to create new users in SCM-Manager. New users don’t have any permissions and should therefore be configured right after they were created.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### User Details Page
|
### User Details Page
|
||||||
The user details page shows the information about the user.
|
The user details page shows the information about the user.
|
||||||
|
|
||||||
The active box shows whether the user is able to use SCM-Manager. The type XML from the shown example indicates that the user was created in SCM-Manager. Users can also be created through external sources, for example based on the information from a LDAP instance that is connected through the LDAP plugin.
|
The active box shows whether the user is able to use SCM-Manager. The external box shows if it is an internal user or whether it is managed by an external system.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -3,7 +3,11 @@ title: User
|
|||||||
subtitle: Settings
|
subtitle: Settings
|
||||||
---
|
---
|
||||||
### General
|
### General
|
||||||
In the general settings the display name, e-mail address and active status of an account can be edited.
|
In the general settings the display name, e-mail address, external flag and active status of an account can be edited.
|
||||||
|
|
||||||
|
If a user is converted from internal to external the password is going to be removed. When switching an external user to an internal one, a password must be set using the password modal dialogue.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
On the bottom is also a button to delete the user. The deletion is irreversible.
|
On the bottom is also a button to delete the user. The deletion is irreversible.
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
],
|
],
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"version": "2.8.0-SNAPSHOT"
|
"version": "2.10.0-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
"resolutions": {
|
"resolutions": {
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#420d6cfa17a6a8f9bf1a517a2c629dcb332dbe13",
|
"gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#420d6cfa17a6a8f9bf1a517a2c629dcb332dbe13",
|
||||||
"lowlight": "1.13.1"
|
"refractor": "3.2.0",
|
||||||
|
"prismjs": "1.22.0"
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
|
|||||||
10
pom.xml
@@ -32,7 +32,7 @@
|
|||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<description>
|
<description>
|
||||||
The easiest way to share your Git, Mercurial
|
The easiest way to share your Git, Mercurial
|
||||||
and Subversion repositories.
|
and Subversion repositories.
|
||||||
@@ -580,7 +580,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>sonia.scm.maven</groupId>
|
<groupId>sonia.scm.maven</groupId>
|
||||||
<artifactId>smp-maven-plugin</artifactId>
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
<version>1.2.0</version>
|
<version>1.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -915,17 +915,17 @@
|
|||||||
<jaxrs.version>2.1.1</jaxrs.version>
|
<jaxrs.version>2.1.1</jaxrs.version>
|
||||||
<resteasy.version>4.5.8.Final</resteasy.version>
|
<resteasy.version>4.5.8.Final</resteasy.version>
|
||||||
<jersey-client.version>1.19.4</jersey-client.version>
|
<jersey-client.version>1.19.4</jersey-client.version>
|
||||||
<jackson.version>2.11.2</jackson.version>
|
<jackson.version>2.11.3</jackson.version>
|
||||||
<guice.version>4.2.3</guice.version>
|
<guice.version>4.2.3</guice.version>
|
||||||
<jaxb.version>2.3.3</jaxb.version>
|
<jaxb.version>2.3.3</jaxb.version>
|
||||||
<hibernate-validator.version>6.1.5.Final</hibernate-validator.version>
|
<hibernate-validator.version>6.1.6.Final</hibernate-validator.version>
|
||||||
<bouncycastle.version>1.66</bouncycastle.version>
|
<bouncycastle.version>1.66</bouncycastle.version>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
<legman.version>1.6.2</legman.version>
|
<legman.version>1.6.2</legman.version>
|
||||||
|
|
||||||
<!-- webserver -->
|
<!-- webserver -->
|
||||||
<jetty.version>9.4.31.v20200723</jetty.version>
|
<jetty.version>9.4.33.v20201020</jetty.version>
|
||||||
<jetty.maven.version>9.4.30.v20200611</jetty.maven.version>
|
<jetty.maven.version>9.4.30.v20200611</jetty.maven.version>
|
||||||
|
|
||||||
<!-- security libraries -->
|
<!-- security libraries -->
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotation-processor</artifactId>
|
<artifactId>scm-annotation-processor</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<name>scm-annotation-processor</name>
|
<name>scm-annotation-processor</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotations</artifactId>
|
<artifactId>scm-annotations</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- rest api -->
|
<!-- rest api -->
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-annotations</artifactId>
|
<artifactId>scm-annotations</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<name>scm-annotations</name>
|
<name>scm-annotations</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-core</artifactId>
|
<artifactId>scm-core</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<name>scm-core</name>
|
<name>scm-core</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotations</artifactId>
|
<artifactId>scm-annotations</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -227,7 +227,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotation-processor</artifactId>
|
<artifactId>scm-annotation-processor</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,14 @@ public class ScmConfiguration implements Configuration {
|
|||||||
@XmlElement(name = "xsrf-protection")
|
@XmlElement(name = "xsrf-protection")
|
||||||
private boolean enabledXsrfProtection = true;
|
private boolean enabledXsrfProtection = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables user converter.
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
@XmlElement(name = "user-converter")
|
||||||
|
private boolean enabledUserConverter = false;
|
||||||
|
|
||||||
@XmlElement(name = "namespace-strategy")
|
@XmlElement(name = "namespace-strategy")
|
||||||
private String namespaceStrategy = "UsernameNamespaceStrategy";
|
private String namespaceStrategy = "UsernameNamespaceStrategy";
|
||||||
|
|
||||||
@@ -238,6 +246,7 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.loginInfoUrl = other.loginInfoUrl;
|
this.loginInfoUrl = other.loginInfoUrl;
|
||||||
this.releaseFeedUrl = other.releaseFeedUrl;
|
this.releaseFeedUrl = other.releaseFeedUrl;
|
||||||
this.mailDomainName = other.mailDomainName;
|
this.mailDomainName = other.mailDomainName;
|
||||||
|
this.enabledUserConverter = other.enabledUserConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -387,6 +396,17 @@ public class ScmConfiguration implements Configuration {
|
|||||||
return enabledXsrfProtection;
|
return enabledXsrfProtection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the user converter is enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the user converter is enabled
|
||||||
|
* The user converter automatically converts an internal user to external on their first login using an external system like ldap
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public boolean isEnabledUserConverter() {
|
||||||
|
return enabledUserConverter;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEnableProxy() {
|
public boolean isEnableProxy() {
|
||||||
return enableProxy;
|
return enableProxy;
|
||||||
}
|
}
|
||||||
@@ -554,6 +574,16 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.enabledXsrfProtection = enabledXsrfProtection;
|
this.enabledXsrfProtection = enabledXsrfProtection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set {@code true} to enable user converter.
|
||||||
|
*
|
||||||
|
* @param enabledUserConverter {@code true} to enable user converter
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public void setEnabledUserConverter(boolean enabledUserConverter) {
|
||||||
|
this.enabledUserConverter = enabledUserConverter;
|
||||||
|
}
|
||||||
|
|
||||||
public void setNamespaceStrategy(String namespaceStrategy) {
|
public void setNamespaceStrategy(String namespaceStrategy) {
|
||||||
this.namespaceStrategy = namespaceStrategy;
|
this.namespaceStrategy = namespaceStrategy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,30 +21,28 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.net.ahc;
|
package sonia.scm.net.ahc;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.LinkedHashMultimap;
|
import com.google.common.collect.LinkedHashMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import org.apache.shiro.codec.Base64;
|
import org.apache.shiro.codec.Base64;
|
||||||
|
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for http requests.
|
* Base class for http requests.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @param <T> request implementation
|
* @param <T> request implementation
|
||||||
*
|
*
|
||||||
* @since 1.46
|
* @since 1.46
|
||||||
*/
|
*/
|
||||||
public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
||||||
@@ -75,7 +73,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public AdvancedHttpResponse request() throws IOException
|
public AdvancedHttpResponse request() throws IOException
|
||||||
{
|
{
|
||||||
return client.request(this);
|
return client.request(this);
|
||||||
}
|
}
|
||||||
@@ -102,7 +100,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
String auth = Strings.nullToEmpty(username).concat(":").concat(
|
String auth = Strings.nullToEmpty(username).concat(":").concat(
|
||||||
Strings.nullToEmpty(password));
|
Strings.nullToEmpty(password));
|
||||||
|
|
||||||
auth = Base64.encodeToString(auth.getBytes(Charsets.ISO_8859_1));
|
auth = Base64.encodeToString(auth.getBytes(StandardCharsets.ISO_8859_1));
|
||||||
headers.put("Authorization", "Basic ".concat(auth));
|
headers.put("Authorization", "Basic ".concat(auth));
|
||||||
|
|
||||||
return self();
|
return self();
|
||||||
@@ -129,7 +127,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param disableCertificateValidation true to disable certificate validation
|
* @param disableCertificateValidation true to disable certificate validation
|
||||||
*
|
*
|
||||||
* @return request instance
|
* @return request instance
|
||||||
*/
|
*/
|
||||||
public T disableCertificateValidation(boolean disableCertificateValidation)
|
public T disableCertificateValidation(boolean disableCertificateValidation)
|
||||||
@@ -246,6 +244,30 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the kind of span for tracing api.
|
||||||
|
*
|
||||||
|
* @param spanKind kind of span
|
||||||
|
* @return request instance
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public T spanKind(String spanKind) {
|
||||||
|
this.spanKind = spanKind;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables tracing for the request.
|
||||||
|
* This should only be done for internal requests.
|
||||||
|
*
|
||||||
|
* @return request instance
|
||||||
|
*/
|
||||||
|
public T disableTracing() {
|
||||||
|
this.spanKind = null;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -281,6 +303,17 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the kind of span which is used for the trace api.
|
||||||
|
*
|
||||||
|
* @return kind of span
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public String getSpanKind() {
|
||||||
|
return spanKind;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the request decodes gzip compression.
|
* Returns true if the request decodes gzip compression.
|
||||||
*
|
*
|
||||||
@@ -317,7 +350,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
/**
|
/**
|
||||||
* Returns true if the proxy settings are ignored.
|
* Returns true if the proxy settings are ignored.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return true if the proxy settings are ignored
|
* @return true if the proxy settings are ignored
|
||||||
*/
|
*/
|
||||||
public boolean isIgnoreProxySettings()
|
public boolean isIgnoreProxySettings()
|
||||||
@@ -341,7 +374,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns string representation of the given object or {@code null}, if the
|
* Returns string representation of the given object or {@code null}, if the
|
||||||
* object is {@code null}.
|
* object is {@code null}.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@@ -398,4 +431,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
|
|||||||
|
|
||||||
/** url of request */
|
/** url of request */
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
|
/** kind of span for trace api */
|
||||||
|
private String spanKind = "HTTP Request";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,18 +21,20 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import sonia.scm.Validateable;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -44,9 +46,14 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "branch")
|
@XmlRootElement(name = "branch")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public final class Branch implements Serializable
|
public final class Branch implements Serializable, Validateable
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
||||||
|
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
|
||||||
|
public static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
|
||||||
|
public static final Pattern VALID_BRANCH_NAME_PATTERN = Pattern.compile(VALID_BRANCH_NAMES);
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final long serialVersionUID = -4602244691711222413L;
|
private static final long serialVersionUID = -4602244691711222413L;
|
||||||
|
|
||||||
@@ -83,6 +90,11 @@ public final class Branch implements Serializable
|
|||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return VALID_BRANCH_NAME_PATTERN.matcher(name).matches();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import sonia.scm.repository.Repository;
|
|||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.security.Authentications;
|
||||||
import sonia.scm.user.EMail;
|
import sonia.scm.user.EMail;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -467,7 +468,8 @@ public final class RepositoryService implements Closeable {
|
|||||||
public Stream<ScmProtocol> getSupportedProtocols() {
|
public Stream<ScmProtocol> getSupportedProtocols() {
|
||||||
return protocolProviders.stream()
|
return protocolProviders.stream()
|
||||||
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
|
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
|
||||||
.map(this::createProviderInstanceForRepository);
|
.map(this::createProviderInstanceForRepository)
|
||||||
|
.filter(protocol -> !Authentications.isAuthenticatedSubjectAnonymous() || protocol.isAnonymousEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "java:S3740"})
|
@SuppressWarnings({"rawtypes", "java:S3740"})
|
||||||
|
|||||||
@@ -40,4 +40,11 @@ public interface ScmProtocol {
|
|||||||
* The URL to access the repository providing this protocol.
|
* The URL to access the repository providing this protocol.
|
||||||
*/
|
*/
|
||||||
String getUrl();
|
String getUrl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the protocol can be used as an anonymous user.
|
||||||
|
*/
|
||||||
|
default boolean isAnonymousEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,11 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||||
@@ -33,10 +34,14 @@ import sonia.scm.NotFoundException;
|
|||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupManager;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
|
import sonia.scm.user.ExternalUserConverter;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.security.AdministrationContext;
|
import sonia.scm.web.security.AdministrationContext;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated
|
* Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated
|
||||||
* users with the local database.
|
* users with the local database.
|
||||||
@@ -44,34 +49,49 @@ import sonia.scm.web.security.AdministrationContext;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Extension
|
@Extension
|
||||||
public final class SyncingRealmHelper {
|
public final class SyncingRealmHelper {
|
||||||
|
|
||||||
private final AdministrationContext ctx;
|
private final AdministrationContext ctx;
|
||||||
private final UserManager userManager;
|
private final UserManager userManager;
|
||||||
private final GroupManager groupManager;
|
private final GroupManager groupManager;
|
||||||
|
private final Set<ExternalUserConverter> externalUserConverters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new SyncingRealmHelper.
|
* Constructs a new SyncingRealmHelper.
|
||||||
*
|
*
|
||||||
* @param ctx administration context
|
* @param ctx administration context
|
||||||
* @param userManager user manager
|
* @param userManager user manager
|
||||||
* @param groupManager group manager
|
* @param groupManager group manager
|
||||||
|
* @param externalUserConverters global scm configuration
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) {
|
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager, Set<ExternalUserConverter> externalUserConverters) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.userManager = userManager;
|
this.userManager = userManager;
|
||||||
this.groupManager = groupManager;
|
this.groupManager = groupManager;
|
||||||
|
this.externalUserConverters = externalUserConverters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new SyncingRealmHelper.
|
||||||
|
*
|
||||||
|
* @param ctx administration context
|
||||||
|
* @param userManager user manager
|
||||||
|
* @param groupManager group manager
|
||||||
|
* @deprecated Use {@link #SyncingRealmHelper(AdministrationContext, UserManager, GroupManager, Set)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) {
|
||||||
|
this(ctx, userManager, groupManager, Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create {@link AuthenticationInfo} from user and groups.
|
* Create {@link AuthenticationInfo} from user and groups.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param realm name of the realm
|
* @param realm name of the realm
|
||||||
* @param user authenticated user
|
* @param user authenticated user
|
||||||
*
|
|
||||||
* @return authentication info
|
* @return authentication info
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo createAuthenticationInfo(String realm, User user) {
|
public AuthenticationInfo createAuthenticationInfo(String realm, User user) {
|
||||||
@@ -91,17 +111,9 @@ public final class SyncingRealmHelper {
|
|||||||
public void store(final Group group) {
|
public void store(final Group group) {
|
||||||
ctx.runAsAdmin(() -> {
|
ctx.runAsAdmin(() -> {
|
||||||
if (groupManager.get(group.getId()) != null) {
|
if (groupManager.get(group.getId()) != null) {
|
||||||
try {
|
modifyGroup(group);
|
||||||
groupManager.modify(group);
|
|
||||||
} catch (NotFoundException e) {
|
|
||||||
throw new IllegalStateException("got NotFoundException though group " + group.getName() + " could be loaded", e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
createNewGroup(group);
|
||||||
groupManager.create(group);
|
|
||||||
} catch (AlreadyExistsException e) {
|
|
||||||
throw new IllegalStateException("got AlreadyExistsException though group " + group.getName() + " could not be loaded", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -114,19 +126,54 @@ public final class SyncingRealmHelper {
|
|||||||
public void store(final User user) {
|
public void store(final User user) {
|
||||||
ctx.runAsAdmin(() -> {
|
ctx.runAsAdmin(() -> {
|
||||||
if (userManager.contains(user.getName())) {
|
if (userManager.contains(user.getName())) {
|
||||||
try {
|
modifyUser(user);
|
||||||
userManager.modify(user);
|
|
||||||
} catch (NotFoundException e) {
|
|
||||||
throw new IllegalStateException("got NotFoundException though user " + user.getName() + " could be loaded", e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
createNewUser(user);
|
||||||
userManager.create(user);
|
|
||||||
} catch (AlreadyExistsException e) {
|
|
||||||
throw new IllegalStateException("got AlreadyExistsException though user " + user.getName() + " could not be loaded", e);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createNewUser(User user) {
|
||||||
|
try {
|
||||||
|
User clone = user.clone();
|
||||||
|
// New user created by syncing realm helper is always external
|
||||||
|
clone.setExternal(true);
|
||||||
|
userManager.create(clone);
|
||||||
|
} catch (AlreadyExistsException e) {
|
||||||
|
throw new IllegalStateException("got AlreadyExistsException though user " + user.getName() + " could not be loaded", e);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void modifyUser(User user) {
|
||||||
|
User clone = user.clone();
|
||||||
|
if (!externalUserConverters.isEmpty()) {
|
||||||
|
log.debug("execute available user converters");
|
||||||
|
for (ExternalUserConverter converter : externalUserConverters) {
|
||||||
|
clone = converter.convert(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
userManager.modify(clone);
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
throw new IllegalStateException("got NotFoundException though user " + clone.getName() + " could be loaded", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewGroup(Group group) {
|
||||||
|
try {
|
||||||
|
groupManager.create(group);
|
||||||
|
} catch (AlreadyExistsException e) {
|
||||||
|
throw new IllegalStateException("got AlreadyExistsException though group " + group.getName() + " could not be loaded", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void modifyGroup(Group group) {
|
||||||
|
try {
|
||||||
|
groupManager.modify(group);
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
throw new IllegalStateException("got NotFoundException though group " + group.getName() + " could be loaded", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
scm-core/src/main/java/sonia/scm/trace/Exporter.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.trace;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exporter could be used to collect and process spans.
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface Exporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the collected span.
|
||||||
|
*
|
||||||
|
* @param span collected span
|
||||||
|
*/
|
||||||
|
void export(SpanContext span);
|
||||||
|
}
|
||||||
141
scm-core/src/main/java/sonia/scm/trace/Span.java
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.trace;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A span represents a single unit of work e.g. a request to an external system.
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public final class Span implements AutoCloseable {
|
||||||
|
|
||||||
|
private final Tracer tracer;
|
||||||
|
private final String kind;
|
||||||
|
private final Map<String,String> labels = new LinkedHashMap<>();
|
||||||
|
private final Instant opened;
|
||||||
|
private boolean failed;
|
||||||
|
|
||||||
|
Span(Tracer tracer, String kind) {
|
||||||
|
this.tracer = tracer;
|
||||||
|
this.kind = kind;
|
||||||
|
this.opened = Instant.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
*
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, String value) {
|
||||||
|
labels.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, int value) {
|
||||||
|
return label(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, long value) {
|
||||||
|
return label(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, float value) {
|
||||||
|
return label(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, double value) {
|
||||||
|
return label(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, boolean value) {
|
||||||
|
return label(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a label to the span.
|
||||||
|
* @param key key of label
|
||||||
|
* @param value label value
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span label(String key, Object value) {
|
||||||
|
return label(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the span as failed.
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Span failed() {
|
||||||
|
failed = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the span a reports the context to the {@link Tracer}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
tracer.export(new SpanContext(kind, Collections.unmodifiableMap(labels), opened, Instant.now(), failed));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
scm-core/src/main/java/sonia/scm/trace/SpanContext.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.trace;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import sonia.scm.xml.XmlInstantAdapter;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SpanContext} represents a finished span which could be processed by an {@link Exporter}.
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@XmlRootElement
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@AllArgsConstructor
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
public class SpanContext {
|
||||||
|
|
||||||
|
private String kind;
|
||||||
|
private Map<String, String> labels;
|
||||||
|
@XmlJavaTypeAdapter(XmlInstantAdapter.class)
|
||||||
|
private Instant opened;
|
||||||
|
@XmlJavaTypeAdapter(XmlInstantAdapter.class)
|
||||||
|
private Instant closed;
|
||||||
|
private boolean failed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the label with the given key or {@code null}.
|
||||||
|
* @param key key of label
|
||||||
|
* @return label or {@code null}
|
||||||
|
*/
|
||||||
|
public String label(String key) {
|
||||||
|
return labels.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the duration of the span.
|
||||||
|
*
|
||||||
|
* @return duration of the span
|
||||||
|
*/
|
||||||
|
public Duration duration() {
|
||||||
|
return Duration.between(opened, closed);
|
||||||
|
}
|
||||||
|
}
|
||||||
83
scm-core/src/main/java/sonia/scm/trace/Tracer.java
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.trace;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tracer api allows the tracing of long running tasks, such as calling external systems.
|
||||||
|
* The api is able to collect tracing points called spans.
|
||||||
|
*
|
||||||
|
* To use the tracer api inject the {@link Tracer} and open a span in a try with resources block e.g.:
|
||||||
|
* <pre>
|
||||||
|
* try (Span span = tracer.span("jenkins").label("repository", "builds/core")) {
|
||||||
|
* Response response = jenkins.call("http://...");
|
||||||
|
* if (!response.isSuccess()) {
|
||||||
|
* span.label("reason", response.getFailedReason());
|
||||||
|
* span.failed();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* As seen in the example we can mark span as failed and add more context to the span with labels.
|
||||||
|
* After a span is closed it is delegated to an {@link Exporter}, which
|
||||||
|
*
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public final class Tracer {
|
||||||
|
|
||||||
|
private final Set<Exporter> exporters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new tracer with the given set of exporters.
|
||||||
|
*
|
||||||
|
* @param exporters set of exporters
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
public Tracer(Set<Exporter> exporters) {
|
||||||
|
this.exporters = exporters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new span.
|
||||||
|
* @param kind kind of span
|
||||||
|
* @return new span
|
||||||
|
*/
|
||||||
|
public Span span(String kind) {
|
||||||
|
return new Span(this, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass the finished span to the exporters.
|
||||||
|
*
|
||||||
|
* @param span finished span
|
||||||
|
*/
|
||||||
|
void export(SpanContext span) {
|
||||||
|
for (Exporter exporter : exporters) {
|
||||||
|
exporter.export(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The external user converter can be used to modify users
|
||||||
|
* which are provided by external systems before creation in SCM-Manager.
|
||||||
|
* The implementations will be called in the {@link sonia.scm.security.SyncingRealmHelper}
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface ExternalUserConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the converted user.
|
||||||
|
* @return converted user
|
||||||
|
*/
|
||||||
|
User convert(User user);
|
||||||
|
}
|
||||||
@@ -24,12 +24,14 @@
|
|||||||
|
|
||||||
package sonia.scm.user;
|
package sonia.scm.user;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.github.sdorra.ssp.PermissionObject;
|
import com.github.sdorra.ssp.PermissionObject;
|
||||||
import com.github.sdorra.ssp.StaticPermissions;
|
import com.github.sdorra.ssp.StaticPermissions;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
import sonia.scm.BasicPropertiesAware;
|
import sonia.scm.BasicPropertiesAware;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.ReducedModelObject;
|
import sonia.scm.ReducedModelObject;
|
||||||
@@ -41,12 +43,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
|||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@StaticPermissions(
|
@StaticPermissions(
|
||||||
value = "user",
|
value = "user",
|
||||||
globalPermissions = {"create", "list", "autocomplete"},
|
globalPermissions = {"create", "list", "autocomplete"},
|
||||||
@@ -55,57 +51,42 @@ import java.security.Principal;
|
|||||||
)
|
)
|
||||||
@XmlRootElement(name = "users")
|
@XmlRootElement(name = "users")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject
|
@Getter
|
||||||
{
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject {
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final long serialVersionUID = -3089541936726329663L;
|
private static final long serialVersionUID = -3089541936726329663L;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private boolean active = true;
|
||||||
|
private boolean external;
|
||||||
|
private Long creationDate;
|
||||||
|
private String displayName;
|
||||||
|
private Long lastModified;
|
||||||
|
private String mail;
|
||||||
|
private String name;
|
||||||
|
private String password;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs ...
|
* The user type is replaced by {@link #external} flag
|
||||||
*
|
* @deprecated Use {@link #external} instead.
|
||||||
*/
|
*/
|
||||||
public User() {}
|
@Deprecated
|
||||||
|
private String type;
|
||||||
|
|
||||||
/**
|
public User(String name) {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
public User(String name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.displayName = name;
|
this.displayName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public User(String name, String displayName, String mail) {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param displayName
|
|
||||||
* @param mail
|
|
||||||
*/
|
|
||||||
public User(String name, String displayName, String mail)
|
|
||||||
{
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.mail = mail;
|
this.mail = mail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public User(String name, String displayName, String mail, String password, String type, boolean active) {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param displayName
|
|
||||||
* @param mail
|
|
||||||
*/
|
|
||||||
public User(String name, String displayName, String mail, String password, String type, boolean active)
|
|
||||||
{
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.mail = mail;
|
this.mail = mail;
|
||||||
@@ -114,90 +95,57 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
|
|||||||
this.active = active;
|
this.active = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public User clone()
|
public User clone() {
|
||||||
{
|
User user;
|
||||||
User user = null;
|
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
user = (User) super.clone();
|
user = (User) super.clone();
|
||||||
}
|
} catch (CloneNotSupportedException ex) {
|
||||||
catch (CloneNotSupportedException ex)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean copyProperties(User user) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean copyProperties(User user)
|
|
||||||
{
|
|
||||||
return copyProperties(user, true);
|
return copyProperties(user, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean copyProperties(User user, boolean copyPassword) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* @param copyPassword
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean copyProperties(User user, boolean copyPassword)
|
|
||||||
{
|
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
|
|
||||||
if (user.isActive() != active)
|
if (user.isActive() != active) {
|
||||||
{
|
|
||||||
result = true;
|
result = true;
|
||||||
user.setActive(active);
|
user.setActive(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.isNotEquals(user.getDisplayName(), displayName))
|
if (user.isExternal() != external) {
|
||||||
{
|
result = true;
|
||||||
|
user.setExternal(external);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Util.isNotEquals(user.getDisplayName(), displayName)) {
|
||||||
result = true;
|
result = true;
|
||||||
user.setDisplayName(displayName);
|
user.setDisplayName(displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.isNotEquals(user.getMail(), mail))
|
if (Util.isNotEquals(user.getMail(), mail)) {
|
||||||
{
|
|
||||||
result = true;
|
result = true;
|
||||||
user.setMail(mail);
|
user.setMail(mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.isNotEquals(user.getName(), name))
|
if (Util.isNotEquals(user.getName(), name)) {
|
||||||
{
|
|
||||||
result = true;
|
result = true;
|
||||||
user.setName(name);
|
user.setName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copyPassword && Util.isNotEquals(user.getPassword(), password))
|
if (copyPassword && Util.isNotEquals(user.getPassword(), password)) {
|
||||||
{
|
|
||||||
result = true;
|
result = true;
|
||||||
user.setPassword(password);
|
user.setPassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.isNotEquals(user.getType(), type))
|
if (Util.isNotEquals(user.getType(), type)) {
|
||||||
{
|
|
||||||
result = true;
|
result = true;
|
||||||
user.setType(type);
|
user.setType(type);
|
||||||
}
|
}
|
||||||
@@ -205,316 +153,65 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param obj
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj)
|
public boolean equals(Object obj) {
|
||||||
{
|
if (obj == null) {
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final User other = (User) obj;
|
final User other = (User) obj;
|
||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(displayName, other.displayName)
|
&& Objects.equal(displayName, other.displayName)
|
||||||
&& Objects.equal(mail, other.mail)
|
&& Objects.equal(mail, other.mail)
|
||||||
&& Objects.equal(type, other.type)
|
&& Objects.equal(external, other.external)
|
||||||
&& Objects.equal(active, other.active)
|
&& Objects.equal(active, other.active)
|
||||||
&& Objects.equal(password, other.password)
|
&& Objects.equal(password, other.password)
|
||||||
&& Objects.equal(creationDate, other.creationDate)
|
&& Objects.equal(creationDate, other.creationDate)
|
||||||
&& Objects.equal(lastModified, other.lastModified)
|
&& Objects.equal(lastModified, other.lastModified)
|
||||||
&& Objects.equal(properties, other.properties);
|
&& Objects.equal(properties, other.properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode() {
|
||||||
{
|
return Objects.hashCode(name, displayName, mail, password,
|
||||||
return Objects.hashCode(name, displayName, mail, type, password,
|
active, external, creationDate, lastModified, properties);
|
||||||
active, creationDate, lastModified, properties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString() {
|
||||||
{
|
|
||||||
String pwd = (password != null)
|
String pwd = (password != null)
|
||||||
? "(is set)"
|
? "(is set)"
|
||||||
: "(not set)";
|
: "(not set)";
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("name", name)
|
.add("name", name)
|
||||||
.add("displayName",displayName)
|
.add("displayName", displayName)
|
||||||
.add("mail", mail)
|
.add("mail", mail)
|
||||||
.add("password", pwd)
|
.add("password", pwd)
|
||||||
.add("type", type)
|
.add("type", type)
|
||||||
.add("active", active)
|
.add("active", active)
|
||||||
.add("creationDate", creationDate)
|
.add("external", external)
|
||||||
.add("lastModified", lastModified)
|
.add("creationDate", creationDate)
|
||||||
.add("properties", properties)
|
.add("lastModified", lastModified)
|
||||||
.toString();
|
.add("properties", properties)
|
||||||
|
.toString();
|
||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Long getCreationDate()
|
|
||||||
{
|
|
||||||
return creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getDisplayName()
|
|
||||||
{
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getId()
|
public boolean isValid() {
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Long getLastModified()
|
|
||||||
{
|
|
||||||
return lastModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getMail()
|
|
||||||
{
|
|
||||||
return mail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getPassword()
|
|
||||||
{
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getType()
|
|
||||||
{
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns false if the user is deactivated.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return false if the user is deactivated
|
|
||||||
* @since 1.16
|
|
||||||
*/
|
|
||||||
public boolean isActive()
|
|
||||||
{
|
|
||||||
return active;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean isValid()
|
|
||||||
{
|
|
||||||
return ValidationUtil.isNameValid(name) && Util.isNotEmpty(displayName)
|
return ValidationUtil.isNameValid(name) && Util.isNotEmpty(displayName)
|
||||||
&& Util.isNotEmpty(type)
|
&& ((Util.isEmpty(mail)) || ValidationUtil.isMailAddressValid(mail));
|
||||||
&& ((Util.isEmpty(mail)) || ValidationUtil.isMailAddressValid(mail));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
@Override
|
||||||
|
public String getId() {
|
||||||
/**
|
return name;
|
||||||
* Activate or deactive this user.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param active false to deactivate the user.
|
|
||||||
* @since 1.6
|
|
||||||
*/
|
|
||||||
public void setActive(boolean active)
|
|
||||||
{
|
|
||||||
this.active = active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param creationDate
|
|
||||||
*/
|
|
||||||
public void setCreationDate(Long creationDate)
|
|
||||||
{
|
|
||||||
this.creationDate = creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param displayName
|
|
||||||
*/
|
|
||||||
public void setDisplayName(String displayName)
|
|
||||||
{
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param lastModified
|
|
||||||
*/
|
|
||||||
public void setLastModified(Long lastModified)
|
|
||||||
{
|
|
||||||
this.lastModified = lastModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param mail
|
|
||||||
*/
|
|
||||||
public void setMail(String mail)
|
|
||||||
{
|
|
||||||
this.mail = mail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
public void setName(String name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param password
|
|
||||||
*/
|
|
||||||
public void setPassword(String password)
|
|
||||||
{
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
*/
|
|
||||||
public void setType(String type)
|
|
||||||
{
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private boolean active = true;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Long creationDate;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String displayName;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Long lastModified;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String mail;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String type;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.google.common.collect.ComparisonChain;
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -43,6 +45,8 @@ import java.util.Locale;
|
|||||||
public final class Version implements Comparable<Version>
|
public final class Version implements Comparable<Version>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final Pattern MAVEN_UNIQUE_SNAPSHOT = Pattern.compile("-[0-9]{8}\\.[0-9]{6}-[0-9]+");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new version object
|
* Constructs a new version object
|
||||||
*
|
*
|
||||||
@@ -388,10 +392,15 @@ public final class Version implements Comparable<Version>
|
|||||||
{
|
{
|
||||||
String qualifier = qualifierPart.trim().toLowerCase(Locale.ENGLISH);
|
String qualifier = qualifierPart.trim().toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
if (qualifier.contains("snapshot"))
|
if (qualifier.contains("snapshot")) {
|
||||||
{
|
|
||||||
snapshot = true;
|
snapshot = true;
|
||||||
qualifier = qualifier.replace("snapshot", "");
|
qualifier = qualifier.replace("snapshot", "");
|
||||||
|
} else {
|
||||||
|
Matcher matcher = MAVEN_UNIQUE_SNAPSHOT.matcher(qualifier);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
snapshot = true;
|
||||||
|
qualifier = matcher.replaceAll("-");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qualifier.length() > 0)
|
if (qualifier.length() > 0)
|
||||||
|
|||||||
@@ -21,23 +21,19 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The software agent that is acting on behalf of a user. The user agent
|
* The software agent that is acting on behalf of a user. The user agent
|
||||||
* represents a browser or one of the repository client (svn, git or hg).
|
* represents a browser or one of the repository client (svn, git or hg).
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra <s.sdorra@gmail.com>
|
* @author Sebastian Sdorra <s.sdorra@gmail.com>
|
||||||
@@ -49,17 +45,16 @@ public final class UserAgent
|
|||||||
/**
|
/**
|
||||||
* Constructs a new user agent
|
* Constructs a new user agent
|
||||||
*
|
*
|
||||||
*
|
* @param name
|
||||||
* @param name
|
|
||||||
* @param browser
|
|
||||||
* @param basicAuthenticationCharset
|
* @param basicAuthenticationCharset
|
||||||
|
* @param browser
|
||||||
*/
|
*/
|
||||||
private UserAgent(String name, boolean browser,
|
private UserAgent(String name, Charset basicAuthenticationCharset, boolean browser, boolean scmClient)
|
||||||
Charset basicAuthenticationCharset)
|
|
||||||
{
|
{
|
||||||
this.name = checkNotNull(name);
|
this.name = checkNotNull(name);
|
||||||
this.browser = browser;
|
|
||||||
this.basicAuthenticationCharset = checkNotNull(basicAuthenticationCharset);
|
this.basicAuthenticationCharset = checkNotNull(basicAuthenticationCharset);
|
||||||
|
this.browser = browser;
|
||||||
|
this.scmClient = scmClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -71,8 +66,30 @@ public final class UserAgent
|
|||||||
* @param name name of the UserAgent
|
* @param name name of the UserAgent
|
||||||
*
|
*
|
||||||
* @return builder for UserAgent
|
* @return builder for UserAgent
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #browser(String)}, {@link #scmClient(String)} or {@link #other(String)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static Builder builder(String name)
|
public static Builder builder(String name)
|
||||||
|
{
|
||||||
|
return other(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder browser(String name)
|
||||||
|
{
|
||||||
|
final Builder builder = new Builder(name);
|
||||||
|
builder.browser = true;
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder scmClient(String name)
|
||||||
|
{
|
||||||
|
final Builder builder = new Builder(name);
|
||||||
|
builder.scmClient = true;
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder other(String name)
|
||||||
{
|
{
|
||||||
return new Builder(name);
|
return new Builder(name);
|
||||||
}
|
}
|
||||||
@@ -97,7 +114,7 @@ public final class UserAgent
|
|||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(browser, other.browser)
|
&& Objects.equal(browser, other.browser)
|
||||||
&& Objects.equal(basicAuthenticationCharset, basicAuthenticationCharset);
|
&& Objects.equal(basicAuthenticationCharset, other.basicAuthenticationCharset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +144,7 @@ public final class UserAgent
|
|||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link Charset}, which is used to decode the basic
|
* Returns the {@link Charset}, which is used to decode the basic
|
||||||
* authentication header.
|
* authentication header.
|
||||||
*
|
*
|
||||||
* @return {@link Charset} for basic authentication
|
* @return {@link Charset} for basic authentication
|
||||||
@@ -152,13 +169,23 @@ public final class UserAgent
|
|||||||
* Returns {@code true} if UserAgent is a browser.
|
* Returns {@code true} if UserAgent is a browser.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return {@code true} if UserAgent is a browser
|
* @return {@code true} if UserAgent is a browser
|
||||||
*/
|
*/
|
||||||
public boolean isBrowser()
|
public boolean isBrowser()
|
||||||
{
|
{
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if UserAgent is an scm client (e.g. git, svn or hg).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return {@code true} if UserAgent is an scm client
|
||||||
|
*/
|
||||||
|
public boolean isScmClient() {
|
||||||
|
return scmClient;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
//~--- inner classes --------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,7 +231,10 @@ public final class UserAgent
|
|||||||
* @param browser {@code true} for a browser
|
* @param browser {@code true} for a browser
|
||||||
*
|
*
|
||||||
* @return {@code this}
|
* @return {@code this}
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #browser(String)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public Builder browser(boolean browser)
|
public Builder browser(boolean browser)
|
||||||
{
|
{
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
@@ -215,12 +245,11 @@ public final class UserAgent
|
|||||||
/**
|
/**
|
||||||
* Builds the {@link UserAgent}.
|
* Builds the {@link UserAgent}.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return new {@link UserAgent}
|
* @return new {@link UserAgent}
|
||||||
*/
|
*/
|
||||||
public UserAgent build()
|
public UserAgent build()
|
||||||
{
|
{
|
||||||
return new UserAgent(name, browser, basicAuthenticationCharset);
|
return new UserAgent(name, basicAuthenticationCharset, browser, scmClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
//~--- fields -------------------------------------------------------------
|
||||||
@@ -229,10 +258,13 @@ public final class UserAgent
|
|||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
/** indicator for browsers */
|
/** indicator for browsers */
|
||||||
private boolean browser = true;
|
private boolean browser = false;
|
||||||
|
|
||||||
|
/** indicator for browsers */
|
||||||
|
private boolean scmClient = false;
|
||||||
|
|
||||||
/** basic authentication charset */
|
/** basic authentication charset */
|
||||||
private Charset basicAuthenticationCharset = Charsets.ISO_8859_1;
|
private Charset basicAuthenticationCharset = StandardCharsets.ISO_8859_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -244,6 +276,9 @@ public final class UserAgent
|
|||||||
/** indicator for browsers */
|
/** indicator for browsers */
|
||||||
private final boolean browser;
|
private final boolean browser;
|
||||||
|
|
||||||
|
/** indicator for scm clients (e.g. git, hg, svn) */
|
||||||
|
private final boolean scmClient;
|
||||||
|
|
||||||
/** name of UserAgent */
|
/** name of UserAgent */
|
||||||
private final String name;
|
private final String name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public final class UserAgentParser
|
|||||||
|
|
||||||
/** unknown UserAgent */
|
/** unknown UserAgent */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final UserAgent UNKNOWN = UserAgent.builder("UNKNOWN").build();
|
static final UserAgent UNKNOWN = UserAgent.other("UNKNOWN").build();
|
||||||
|
|
||||||
/** logger */
|
/** logger */
|
||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
|
|||||||
@@ -24,7 +24,15 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.api;
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.spi.HttpScmProtocol;
|
import sonia.scm.repository.spi.HttpScmProtocol;
|
||||||
@@ -42,16 +50,32 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
|||||||
import static org.assertj.core.util.IterableUtil.sizeOf;
|
import static org.assertj.core.util.IterableUtil.sizeOf;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class RepositoryServiceTest {
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class RepositoryServiceTest {
|
||||||
|
|
||||||
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
|
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
|
||||||
private final Repository repository = new Repository("", "git", "space", "repo");
|
private final Repository repository = new Repository("", "git", "space", "repo");
|
||||||
|
|
||||||
private final EMail eMail = new EMail(new ScmConfiguration());
|
private final EMail eMail = new EMail(new ScmConfiguration());
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void bindSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void unbindSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnMatchingProtocolsFromProvider() {
|
void shouldReturnMatchingProtocolsFromProvider() {
|
||||||
|
when(subject.getPrincipal()).thenReturn("Hitchhiker");
|
||||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
||||||
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
||||||
|
|
||||||
@@ -59,7 +83,17 @@ public class RepositoryServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFindKnownProtocol() {
|
void shouldFilterOutNonAnonymousEnabledProtocolsForAnonymousUser() {
|
||||||
|
when(subject.getPrincipal()).thenReturn(SCMContext.USER_ANONYMOUS);
|
||||||
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Stream.of(new DummyScmProtocolProvider(), new DummyScmProtocolProvider(false)).collect(Collectors.toSet()), null, eMail);
|
||||||
|
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
||||||
|
|
||||||
|
assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindKnownProtocol() {
|
||||||
|
when(subject.getPrincipal()).thenReturn("Hitchhiker");
|
||||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
||||||
|
|
||||||
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
||||||
@@ -68,23 +102,44 @@ public class RepositoryServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFailForUnknownProtocol() {
|
void shouldFailForUnknownProtocol() {
|
||||||
|
when(subject.getPrincipal()).thenReturn("Hitchhiker");
|
||||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> repositoryService.getProtocol(UnknownScmProtocol.class));
|
assertThrows(IllegalArgumentException.class, () -> repositoryService.getProtocol(UnknownScmProtocol.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DummyHttpProtocol extends HttpScmProtocol {
|
private static class DummyHttpProtocol extends HttpScmProtocol {
|
||||||
public DummyHttpProtocol(Repository repository) {
|
|
||||||
|
private final boolean anonymousEnabled;
|
||||||
|
|
||||||
|
public DummyHttpProtocol(Repository repository, boolean anonymousEnabled) {
|
||||||
super(repository, "");
|
super(repository, "");
|
||||||
|
this.anonymousEnabled = anonymousEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
|
public void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAnonymousEnabled() {
|
||||||
|
return anonymousEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DummyScmProtocolProvider implements ScmProtocolProvider {
|
private static class DummyScmProtocolProvider implements ScmProtocolProvider {
|
||||||
|
|
||||||
|
private final boolean anonymousEnabled;
|
||||||
|
|
||||||
|
public DummyScmProtocolProvider() {
|
||||||
|
this(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummyScmProtocolProvider(boolean anonymousEnabled) {
|
||||||
|
this.anonymousEnabled = anonymousEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "git";
|
return "git";
|
||||||
@@ -92,9 +147,10 @@ public class RepositoryServiceTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScmProtocol get(Repository repository) {
|
public ScmProtocol get(Repository repository) {
|
||||||
return new DummyHttpProtocol(repository);
|
return new DummyHttpProtocol(repository, anonymousEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface UnknownScmProtocol extends ScmProtocol {}
|
private interface UnknownScmProtocol extends ScmProtocol {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,15 +27,18 @@ package sonia.scm.security;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.AlreadyExistsException;
|
import sonia.scm.AlreadyExistsException;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupManager;
|
||||||
|
import sonia.scm.user.ExternalUserConverter;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.security.AdministrationContext;
|
import sonia.scm.web.security.AdministrationContext;
|
||||||
@@ -46,7 +49,9 @@ import java.io.IOException;
|
|||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -68,8 +73,13 @@ public class SyncingRealmHelperTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private UserManager userManager;
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ExternalUserConverter converter;
|
||||||
|
|
||||||
private SyncingRealmHelper helper;
|
private SyncingRealmHelper helper;
|
||||||
|
|
||||||
|
private SyncingRealmHelper helperWithConverters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock {@link AdministrationContext} and create object under test.
|
* Mock {@link AdministrationContext} and create object under test.
|
||||||
*/
|
*/
|
||||||
@@ -94,6 +104,7 @@ public class SyncingRealmHelperTest {
|
|||||||
};
|
};
|
||||||
|
|
||||||
helper = new SyncingRealmHelper(ctx, userManager, groupManager);
|
helper = new SyncingRealmHelper(ctx, userManager, groupManager);
|
||||||
|
helperWithConverters = new SyncingRealmHelper(ctx, userManager, groupManager, ImmutableSet.of(converter));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,10 +151,15 @@ public class SyncingRealmHelperTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testStoreUserCreate() {
|
public void testStoreUserCreate() {
|
||||||
|
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
|
||||||
User user = new User("tricia");
|
User user = new User("tricia");
|
||||||
|
|
||||||
helper.store(user);
|
helper.store(user);
|
||||||
verify(userManager, times(1)).create(user);
|
verify(userManager, times(1)).create(userArgumentCaptor.capture());
|
||||||
|
|
||||||
|
User value = userArgumentCaptor.getValue();
|
||||||
|
assertEquals(user.getDisplayName(), value.getDisplayName());
|
||||||
|
assertEquals(user.getName(), value.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,9 +167,10 @@ public class SyncingRealmHelperTest {
|
|||||||
*/
|
*/
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void testStoreUserFailure() {
|
public void testStoreUserFailure() {
|
||||||
|
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
|
||||||
User user = new User("tricia");
|
User user = new User("tricia");
|
||||||
|
|
||||||
doThrow(AlreadyExistsException.class).when(userManager).create(user);
|
doThrow(AlreadyExistsException.class).when(userManager).create(userArgumentCaptor.capture());
|
||||||
helper.store(user);
|
helper.store(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +187,23 @@ public class SyncingRealmHelperTest {
|
|||||||
verify(userManager, times(1)).modify(user);
|
verify(userManager, times(1)).modify(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SyncingRealmHelper#store(User)} with an existing user.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testConvertUser(){
|
||||||
|
User zaphod = new User("zaphod");
|
||||||
|
when(converter.convert(any())).thenReturn(zaphod);
|
||||||
|
when(userManager.contains("tricia")).thenReturn(Boolean.TRUE);
|
||||||
|
|
||||||
|
User user = new User("tricia");
|
||||||
|
|
||||||
|
helperWithConverters.store(user);
|
||||||
|
|
||||||
|
verify(converter).convert(user);
|
||||||
|
verify(userManager, times(1)).modify(zaphod);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builderShouldSetValues() {
|
public void builderShouldSetValues() {
|
||||||
|
|||||||
57
scm-core/src/test/java/sonia/scm/trace/SpanContextTest.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.trace;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXB;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class SpanContextTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMarshalAndUnmarshal() {
|
||||||
|
Instant now = Instant.now();
|
||||||
|
SpanContext span = new SpanContext(
|
||||||
|
"jenkins", ImmutableMap.of("one", "1"), now, now, true
|
||||||
|
);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
JAXB.marshal(span, baos);
|
||||||
|
span = JAXB.unmarshal(new ByteArrayInputStream(baos.toByteArray()), SpanContext.class);
|
||||||
|
|
||||||
|
assertThat(span.getKind()).isEqualTo("jenkins");
|
||||||
|
assertThat(span.label("one")).isEqualTo("1");
|
||||||
|
assertThat(span.getOpened()).isEqualTo(now);
|
||||||
|
assertThat(span.getClosed()).isEqualTo(now);
|
||||||
|
assertThat(span.isFailed()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
127
scm-core/src/test/java/sonia/scm/trace/TracerTest.java
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.trace;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class TracerTest {
|
||||||
|
|
||||||
|
private Tracer tracer;
|
||||||
|
private CollectingExporter exporter;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpTracer() {
|
||||||
|
exporter = new CollectingExporter();
|
||||||
|
tracer = new Tracer(Collections.singleton(exporter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnSpan() {
|
||||||
|
tracer.span("sample").close();
|
||||||
|
|
||||||
|
SpanContext span = exporter.spans.get(0);
|
||||||
|
assertThat(span.getKind()).isEqualTo("sample");
|
||||||
|
assertThat(span.getOpened()).isNotNull();
|
||||||
|
assertThat(span.getClosed()).isNotNull();
|
||||||
|
assertThat(span.isFailed()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("java:S2925") // it is ok, to use sleep here
|
||||||
|
void shouldReturnPositiveDuration() throws InterruptedException {
|
||||||
|
try (Span span = tracer.span("sample")) {
|
||||||
|
span.label("l1", "one");
|
||||||
|
Thread.sleep(1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanContext span = exporter.spans.get(0);
|
||||||
|
assertThat(span.duration()).isPositive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConvertLabels() {
|
||||||
|
try (Span span = tracer.span("sample")) {
|
||||||
|
span.label("int", 21);
|
||||||
|
span.label("long", 42L);
|
||||||
|
span.label("float", 21.0f);
|
||||||
|
span.label("double", 42.0d);
|
||||||
|
span.label("boolean", true);
|
||||||
|
span.label("object", new StringWrapper("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> labels = exporter.spans.get(0).getLabels();
|
||||||
|
assertThat(labels)
|
||||||
|
.containsEntry("int", "21")
|
||||||
|
.containsEntry("long", "42")
|
||||||
|
.containsEntry("float", "21.0")
|
||||||
|
.containsEntry("double", "42.0")
|
||||||
|
.containsEntry("boolean", "true")
|
||||||
|
.containsEntry("object", "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnFailedSpan() {
|
||||||
|
try (Span span = tracer.span("failing")) {
|
||||||
|
span.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanContext span = exporter.spans.get(0);
|
||||||
|
assertThat(span.getKind()).isEqualTo("failing");
|
||||||
|
assertThat(span.isFailed()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CollectingExporter implements Exporter {
|
||||||
|
|
||||||
|
private final List<SpanContext> spans = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void export(SpanContext spanContext) {
|
||||||
|
spans.add(spanContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StringWrapper {
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
public StringWrapper(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -145,4 +145,11 @@ class VersionTest {
|
|||||||
void testUnparseable() {
|
void testUnparseable() {
|
||||||
assertThrows(VersionParseException.class, () -> Version.parse("aaaa"));
|
assertThrows(VersionParseException.class, () -> Version.parse("aaaa"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDetectUniqueMavenSnapshotVersion() {
|
||||||
|
Version version = Version.parse("1.0.0-20201022.094711-15");
|
||||||
|
assertThat(version.isSnapshot()).isTrue();
|
||||||
|
assertThat(version).hasToString("1.0.0-SNAPSHOT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
@@ -89,7 +89,7 @@ public class UserAgentParserTest
|
|||||||
UserAgent ua = parser.parse(UA_1);
|
UserAgent ua = parser.parse(UA_1);
|
||||||
|
|
||||||
assertEquals(Charsets.ISO_8859_1, ua.getBasicAuthenticationCharset());
|
assertEquals(Charsets.ISO_8859_1, ua.getBasicAuthenticationCharset());
|
||||||
assertTrue(ua.isBrowser());
|
assertFalse(ua.isBrowser());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,11 +99,11 @@ public class UserAgentParserTest
|
|||||||
@Test
|
@Test
|
||||||
public void testParse()
|
public void testParse()
|
||||||
{
|
{
|
||||||
UserAgent ua = UserAgent.builder("UA1").build();
|
UserAgent ua = UserAgent.other("UA1").build();
|
||||||
|
|
||||||
when(provider1.parseUserAgent(UA_1)).thenReturn(ua);
|
when(provider1.parseUserAgent(UA_1)).thenReturn(ua);
|
||||||
|
|
||||||
UserAgent ua2 = UserAgent.builder("UA2").build();
|
UserAgent ua2 = UserAgent.other("UA2").build();
|
||||||
|
|
||||||
when(provider2.parseUserAgent(UA_2)).thenReturn(ua2);
|
when(provider2.parseUserAgent(UA_2)).thenReturn(ua2);
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ public class UserAgentParserTest
|
|||||||
{
|
{
|
||||||
when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(UA_2);
|
when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(UA_2);
|
||||||
|
|
||||||
UserAgent ua = UserAgent.builder("UA2").build();
|
UserAgent ua = UserAgent.other("UA2").build();
|
||||||
|
|
||||||
when(provider1.parseUserAgent(UA_2)).thenReturn(ua);
|
when(provider1.parseUserAgent(UA_2)).thenReturn(ua);
|
||||||
assertEquals(ua, parser.parse(request));
|
assertEquals(ua, parser.parse(request));
|
||||||
@@ -144,7 +144,7 @@ public class UserAgentParserTest
|
|||||||
@Test
|
@Test
|
||||||
public void testParseWithCache()
|
public void testParseWithCache()
|
||||||
{
|
{
|
||||||
UserAgent ua = UserAgent.builder("UA").build();
|
UserAgent ua = UserAgent.other("UA").build();
|
||||||
|
|
||||||
when(cache.get(UA_1)).thenReturn(ua);
|
when(cache.get(UA_1)).thenReturn(ua);
|
||||||
assertEquals(ua, parser.parse(UA_1));
|
assertEquals(ua, parser.parse(UA_1));
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ class HttpProtocolServletAuthenticationFilterBaseTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private FilterChain filterChain;
|
private FilterChain filterChain;
|
||||||
|
|
||||||
private UserAgent nonBrowser = UserAgent.builder("i'm not a browser").browser(false).build();
|
private UserAgent nonBrowser = UserAgent.other("i'm not a browser").build();
|
||||||
private UserAgent browser = UserAgent.builder("i am a browser").browser(true).build();
|
private UserAgent browser = UserAgent.browser("i am a browser").build();
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUpObjectUnderTest() {
|
void setUpObjectUnderTest() {
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-dao-xml</artifactId>
|
<artifactId>scm-dao-xml</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<name>scm-dao-xml</name>
|
<name>scm-dao-xml</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-core</artifactId>
|
<artifactId>scm-core</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- test -->
|
<!-- test -->
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-test</artifactId>
|
<artifactId>scm-test</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -31,40 +31,40 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-it</artifactId>
|
<artifactId>scm-it</artifactId>
|
||||||
<!-- we need type war, because the jetty plugin does not work with jar or pom -->
|
<!-- we need type war, because the jetty plugin does not work with jar or pom -->
|
||||||
<packaging>war</packaging>
|
<packaging>war</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<name>scm-it</name>
|
<name>scm-it</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-core</artifactId>
|
<artifactId>scm-core</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-test</artifactId>
|
<artifactId>scm-test</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-git-plugin</artifactId>
|
<artifactId>scm-git-plugin</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-git-plugin</artifactId>
|
<artifactId>scm-git-plugin</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -72,14 +72,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-hg-plugin</artifactId>
|
<artifactId>scm-hg-plugin</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-hg-plugin</artifactId>
|
<artifactId>scm-hg-plugin</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -87,14 +87,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-svn-plugin</artifactId>
|
<artifactId>scm-svn-plugin</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-svn-plugin</artifactId>
|
<artifactId>scm-svn-plugin</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import org.junit.Before;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
import sonia.scm.it.utils.RepositoryUtil;
|
import sonia.scm.it.utils.RepositoryUtil;
|
||||||
import sonia.scm.it.utils.RestUtil;
|
import sonia.scm.it.utils.RestUtil;
|
||||||
import sonia.scm.it.utils.TestData;
|
import sonia.scm.it.utils.TestData;
|
||||||
@@ -39,16 +41,30 @@ import sonia.scm.web.VndMediaType;
|
|||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
||||||
import static sonia.scm.it.utils.RestUtil.given;
|
import static sonia.scm.it.utils.RestUtil.given;
|
||||||
|
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
|
||||||
import static sonia.scm.it.utils.TestData.WRITE;
|
import static sonia.scm.it.utils.TestData.WRITE;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
public class ApiKeyITCase {
|
public class ApiKeyITCase {
|
||||||
|
|
||||||
|
@Parameterized.Parameters(name = "{0}")
|
||||||
|
public static Collection<String> createParameters() {
|
||||||
|
return availableScmTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String repositoryType;
|
||||||
|
|
||||||
|
public ApiKeyITCase(String repositoryType) {
|
||||||
|
this.repositoryType = repositoryType;
|
||||||
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
@@ -56,7 +72,7 @@ public class ApiKeyITCase {
|
|||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
TestData.createDefault();
|
TestData.createDefault();
|
||||||
TestData.createNotAdminUser("user", "user");
|
TestData.createNotAdminUser("user", "user");
|
||||||
TestData.createUserPermission("user", WRITE, "git");
|
TestData.createUserPermission("user", WRITE, repositoryType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -68,7 +84,7 @@ public class ApiKeyITCase {
|
|||||||
public void shouldCloneWithRestrictedApiKey() throws IOException {
|
public void shouldCloneWithRestrictedApiKey() throws IOException {
|
||||||
String passphrase = registerApiKey();
|
String passphrase = registerApiKey();
|
||||||
|
|
||||||
RepositoryClient client = RepositoryUtil.createRepositoryClient("git", temporaryFolder.newFolder(), "user", passphrase);
|
RepositoryClient client = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), "user", passphrase);
|
||||||
|
|
||||||
assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length);
|
assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length);
|
||||||
}
|
}
|
||||||
@@ -77,7 +93,7 @@ public class ApiKeyITCase {
|
|||||||
public void shouldFailToCommit() throws IOException {
|
public void shouldFailToCommit() throws IOException {
|
||||||
String passphrase = registerApiKey();
|
String passphrase = registerApiKey();
|
||||||
|
|
||||||
RepositoryClient client = RepositoryUtil.createRepositoryClient("git", temporaryFolder.newFolder(), "user", passphrase);
|
RepositoryClient client = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), "user", passphrase);
|
||||||
|
|
||||||
assertThrows(RepositoryClientException.class, () -> addAndCommitRandomFile(client, "user"));
|
assertThrows(RepositoryClientException.class, () -> addAndCommitRandomFile(client, "user"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.it;
|
package sonia.scm.it;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
@@ -185,7 +185,7 @@ public class GitNonFastForwardITCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
|
private static void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
|
||||||
String config = String.format("{'disabled': false, 'gcExpression': null, 'nonFastForwardDisallowed': %s}", nonFastForwardDisallowed)
|
String config = String.format("{'disabled': false, 'gcExpression': null, 'defaultBranch': 'main', 'nonFastForwardDisallowed': %s}", nonFastForwardDisallowed)
|
||||||
.replace('\'', '"');
|
.replace('\'', '"');
|
||||||
|
|
||||||
given(VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX)
|
given(VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX)
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>deb</artifactId>
|
<artifactId>deb</artifactId>
|
||||||
<packaging>deb</packaging>
|
<packaging>deb</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<description>Packaging for Debian/Ubuntu</description>
|
<description>Packaging for Debian/Ubuntu</description>
|
||||||
<name>deb</name>
|
<name>deb</name>
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-server</artifactId>
|
<artifactId>scm-server</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM adoptopenjdk/openjdk11:jdk-11.0.8_10-alpine-slim
|
FROM adoptopenjdk/openjdk11:jdk-11.0.9_11.1-alpine-slim
|
||||||
ENV SCM_HOME=/var/lib/scm
|
ENV SCM_HOME=/var/lib/scm
|
||||||
ENV CACHE_DIR=/var/cache/scm/work
|
ENV CACHE_DIR=/var/cache/scm/work
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>docker</artifactId>
|
<artifactId>docker</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>helm</artifactId>
|
<artifactId>helm</artifactId>
|
||||||
<packaging>helm</packaging>
|
<packaging>helm</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<helm.version>3.2.1</helm.version>
|
<helm.version>3.2.1</helm.version>
|
||||||
|
|||||||
@@ -31,13 +31,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<deployment.serverId>packages.scm-manager.org</deployment.serverId>
|
<deployment.serverId>packages.scm-manager.org</deployment.serverId>
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>release-yaml</artifactId>
|
<artifactId>release-yaml</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>rpm</artifactId>
|
<artifactId>rpm</artifactId>
|
||||||
<packaging>rpm</packaging>
|
<packaging>rpm</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<description>Packaging for RedHat/Centos/Fedora</description>
|
<description>Packaging for RedHat/Centos/Fedora</description>
|
||||||
<name>rpm</name>
|
<name>rpm</name>
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-server</artifactId>
|
<artifactId>scm-server</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>unix</artifactId>
|
<artifactId>unix</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.packaging</groupId>
|
<groupId>sonia.scm.packaging</groupId>
|
||||||
<artifactId>scm-packaging</artifactId>
|
<artifactId>scm-packaging</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>windows</artifactId>
|
<artifactId>windows</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
@@ -71,11 +71,11 @@
|
|||||||
<goal>wget</goal>
|
<goal>wget</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<url>https://github.com/winsw/winsw/releases/download/v2.9.0/WinSW.NETCore31.x64.exe</url>
|
<url>https://github.com/winsw/winsw/releases/download/v2.10.3/WinSW.NETCore31.x86.exe</url>
|
||||||
<unpack>false</unpack>
|
<unpack>false</unpack>
|
||||||
<outputFileName>scm-server.exe</outputFileName>
|
<outputFileName>scm-server.exe</outputFileName>
|
||||||
<outputDirectory>${project.build.directory}/windows</outputDirectory>
|
<outputDirectory>${project.build.directory}/windows</outputDirectory>
|
||||||
<sha256>59d29a41652cfc9a564c9c05d77976391833a6fb686bce941ad89f8f8dff120b</sha256>
|
<sha256>d6ad842e104bfb200bca06d6724e3e1fb19d013fa62fa49a21298d2ee9b044b7</sha256>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|||||||
@@ -31,13 +31,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-plugins</artifactId>
|
<artifactId>scm-plugins</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<name>scm-plugins</name>
|
<name>scm-plugins</name>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-core</artifactId>
|
<artifactId>scm-core</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotation-processor</artifactId>
|
<artifactId>scm-annotation-processor</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-test</artifactId>
|
<artifactId>scm-test</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@scm-manager/scm-git-plugin",
|
"name": "@scm-manager/scm-git-plugin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.8.0-SNAPSHOT",
|
"version": "2.10.0-SNAPSHOT",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./src/main/js/index.ts",
|
"main": "./src/main/js/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@scm-manager/prettier-config",
|
"prettier": "@scm-manager/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scm-manager/ui-plugins": "^2.8.0-SNAPSHOT"
|
"@scm-manager/ui-plugins": "^2.10.0-SNAPSHOT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>scm-plugins</artifactId>
|
<artifactId>scm-plugins</artifactId>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-git-plugin</artifactId>
|
<artifactId>scm-git-plugin</artifactId>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
@@ -29,6 +29,12 @@ import de.otto.edison.hal.Links;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@@ -41,6 +47,11 @@ public class GitConfigDto extends HalRepresentation {
|
|||||||
|
|
||||||
private boolean nonFastForwardDisallowed;
|
private boolean nonFastForwardDisallowed;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
@Length(min = 1, max = 100)
|
||||||
|
@Pattern(regexp = VALID_BRANCH_NAMES)
|
||||||
|
private String defaultBranch;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
protected HalRepresentation add(Links links) {
|
protected HalRepresentation add(Links links) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
@@ -38,6 +38,7 @@ import sonia.scm.web.VndMediaType;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.PUT;
|
import javax.ws.rs.PUT;
|
||||||
@@ -126,7 +127,7 @@ public class GitConfigResource {
|
|||||||
mediaType = VndMediaType.ERROR_TYPE,
|
mediaType = VndMediaType.ERROR_TYPE,
|
||||||
schema = @Schema(implementation = ErrorDto.class)
|
schema = @Schema(implementation = ErrorDto.class)
|
||||||
))
|
))
|
||||||
public Response update(GitConfigDto configDto) {
|
public Response update(@Valid GitConfigDto configDto) {
|
||||||
|
|
||||||
GitConfig config = dtoToConfigMapper.map(configDto);
|
GitConfig config = dtoToConfigMapper.map(configDto);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -33,7 +33,6 @@ import javax.xml.bind.annotation.XmlRootElement;
|
|||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "config")
|
@XmlRootElement(name = "config")
|
||||||
@@ -49,6 +48,9 @@ public class GitConfig extends RepositoryConfig {
|
|||||||
@XmlElement(name = "disallow-non-fast-forward")
|
@XmlElement(name = "disallow-non-fast-forward")
|
||||||
private boolean nonFastForwardDisallowed;
|
private boolean nonFastForwardDisallowed;
|
||||||
|
|
||||||
|
@XmlElement(name = "default-branch")
|
||||||
|
private String defaultBranch = "main";
|
||||||
|
|
||||||
public String getGcExpression() {
|
public String getGcExpression() {
|
||||||
return gcExpression;
|
return gcExpression;
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,14 @@ public class GitConfig extends RepositoryConfig {
|
|||||||
this.nonFastForwardDisallowed = nonFastForwardDisallowed;
|
this.nonFastForwardDisallowed = nonFastForwardDisallowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefaultBranch() {
|
||||||
|
return defaultBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultBranch(String defaultBranch) {
|
||||||
|
this.defaultBranch = defaultBranch;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
|||||||
@@ -55,16 +55,18 @@ import sonia.scm.store.BlobStore;
|
|||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static java.util.Optional.empty;
|
import static java.util.Optional.empty;
|
||||||
@@ -141,12 +143,12 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
if (Util.isEmpty(request.getRevision())) {
|
if (Util.isEmpty(request.getRevision())) {
|
||||||
return getDefaultBranch(repo);
|
return getDefaultBranch(repo);
|
||||||
} else {
|
} else {
|
||||||
ObjectId revId = GitUtil.getRevisionId(repo, request.getRevision());
|
ObjectId revisionId = GitUtil.getRevisionId(repo, request.getRevision());
|
||||||
if (revId == null) {
|
if (revisionId == null) {
|
||||||
logger.error("could not find revision {}", request.getRevision());
|
logger.error("could not find revision {}", request.getRevision());
|
||||||
throw notFound(entity("Revision", request.getRevision()).in(this.repository));
|
throw notFound(entity("Revision", request.getRevision()).in(this.repository));
|
||||||
}
|
}
|
||||||
return revId;
|
return revisionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +214,9 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
|
|
||||||
private FileObject getEntry() throws IOException {
|
private FileObject getEntry() throws IOException {
|
||||||
try (RevWalk revWalk = new RevWalk(repo); TreeWalk treeWalk = new TreeWalk(repo)) {
|
try (RevWalk revWalk = new RevWalk(repo); TreeWalk treeWalk = new TreeWalk(repo)) {
|
||||||
logger.debug("load repository browser for revision {}", revId.name());
|
if (logger.isDebugEnabled()) { // method call in logger call
|
||||||
|
logger.debug("load repository browser for revision {}", revId.name());
|
||||||
|
}
|
||||||
|
|
||||||
if (!isRootRequest()) {
|
if (!isRootRequest()) {
|
||||||
treeWalk.setFilter(PathFilter.create(request.getPath()));
|
treeWalk.setFilter(PathFilter.create(request.getPath()));
|
||||||
@@ -275,7 +279,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createTree(TreeEntry parent, TreeWalk treeWalk) throws IOException {
|
private void createTree(TreeEntry parent, TreeWalk treeWalk) throws IOException {
|
||||||
Stack<TreeEntry> parents = new Stack<>();
|
Deque<TreeEntry> parents = new ArrayDeque<>();
|
||||||
parents.push(parent);
|
parents.push(parent);
|
||||||
while (treeWalk.next()) {
|
while (treeWalk.next()) {
|
||||||
final String currentPath = treeWalk.getPathString();
|
final String currentPath = treeWalk.getPathString();
|
||||||
@@ -283,11 +287,15 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
parents.pop();
|
parents.pop();
|
||||||
}
|
}
|
||||||
TreeEntry currentParent = parents.peek();
|
TreeEntry currentParent = parents.peek();
|
||||||
TreeEntry treeEntry = new TreeEntry(repo, treeWalk);
|
TreeEntry treeEntry = createTreeEntry(repo, treeWalk);
|
||||||
currentParent.addChild(treeEntry);
|
if (treeEntry != null) {
|
||||||
if (request.isRecursive() && treeEntry.getType() == TreeType.DIRECTORY) {
|
currentParent.addChild(treeEntry);
|
||||||
treeWalk.enterSubtree();
|
if (request.isRecursive() && treeEntry.getType() == TreeType.DIRECTORY) {
|
||||||
parents.push(treeEntry);
|
treeWalk.enterSubtree();
|
||||||
|
parents.push(treeEntry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("failed to find tree entry for {}", currentPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,7 +312,12 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
currentDepth++;
|
currentDepth++;
|
||||||
|
|
||||||
if (currentDepth >= limit) {
|
if (currentDepth >= limit) {
|
||||||
return createFileObject(new TreeEntry(repo, treeWalk));
|
TreeEntry treeEntry = createTreeEntry(repo, treeWalk);
|
||||||
|
if (treeEntry != null) {
|
||||||
|
return createFileObject(treeEntry);
|
||||||
|
} else {
|
||||||
|
logger.warn("could not find tree entry at {}", name);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
treeWalk.enterSubtree();
|
treeWalk.enterSubtree();
|
||||||
}
|
}
|
||||||
@@ -328,8 +341,12 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubRepository getSubRepository(String path)
|
@Nullable
|
||||||
throws IOException {
|
private SubRepository getSubRepository(String path) throws IOException {
|
||||||
|
if (request.isDisableSubRepositoryDetection()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, SubRepository> subRepositories = subrepositoryCache.get(revId);
|
Map<String, SubRepository> subRepositories = subrepositoryCache.get(revId);
|
||||||
|
|
||||||
if (subRepositories == null) {
|
if (subRepositories == null) {
|
||||||
@@ -447,6 +464,23 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
FILE, DIRECTORY, SUB_REPOSITORY
|
FILE, DIRECTORY, SUB_REPOSITORY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
TreeEntry createTreeEntry(org.eclipse.jgit.lib.Repository repo, TreeWalk treeWalk) throws IOException {
|
||||||
|
String pathString = treeWalk.getPathString();
|
||||||
|
ObjectId objectId = treeWalk.getObjectId(0);
|
||||||
|
SubRepository subRepository = getSubRepository(pathString);
|
||||||
|
if (subRepository != null) {
|
||||||
|
return new TreeEntry(pathString, treeWalk.getNameString(), objectId, subRepository);
|
||||||
|
} else if (repo.getObjectDatabase().has(objectId)) {
|
||||||
|
TreeType type = TreeType.FILE;
|
||||||
|
if (repo.open(objectId).getType() == Constants.OBJ_TREE) {
|
||||||
|
type = TreeType.DIRECTORY;
|
||||||
|
}
|
||||||
|
return new TreeEntry(pathString, treeWalk.getNameString(), objectId, type);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private class TreeEntry {
|
private class TreeEntry {
|
||||||
|
|
||||||
private final String pathString;
|
private final String pathString;
|
||||||
@@ -466,21 +500,20 @@ public class GitBrowseCommand extends AbstractGitCommand
|
|||||||
type = TreeType.DIRECTORY;
|
type = TreeType.DIRECTORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeEntry(org.eclipse.jgit.lib.Repository repo, TreeWalk treeWalk) throws IOException {
|
TreeEntry(String pathString, String nameString, ObjectId objectId, SubRepository subRepository) {
|
||||||
this.pathString = treeWalk.getPathString();
|
this.pathString = pathString;
|
||||||
this.nameString = treeWalk.getNameString();
|
this.nameString = nameString;
|
||||||
this.objectId = treeWalk.getObjectId(0);
|
this.objectId = objectId;
|
||||||
|
this.type = TreeType.SUB_REPOSITORY;
|
||||||
|
this.subRepository = subRepository;
|
||||||
|
}
|
||||||
|
|
||||||
if (!request.isDisableSubRepositoryDetection() && GitBrowseCommand.this.getSubRepository(pathString) != null) {
|
TreeEntry(String pathString, String nameString, ObjectId objectId, TreeType type) {
|
||||||
subRepository = GitBrowseCommand.this.getSubRepository(pathString);
|
this.pathString = pathString;
|
||||||
type = TreeType.SUB_REPOSITORY;
|
this.nameString = nameString;
|
||||||
} else if (repo.open(objectId).getType() == Constants.OBJ_TREE) {
|
this.objectId = objectId;
|
||||||
subRepository = null;
|
this.type = type;
|
||||||
type = TreeType.DIRECTORY;
|
this.subRepository = null;
|
||||||
} else {
|
|
||||||
subRepository = null;
|
|
||||||
type = TreeType.FILE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPathString() {
|
String getPathString() {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitRepositoryConfig;
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
@@ -49,20 +50,12 @@ public class GitContext implements Closeable, RepositoryProvider
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(GitContext.class);
|
LoggerFactory.getLogger(GitContext.class);
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider, GitConfig config)
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param directory
|
|
||||||
* @param repository
|
|
||||||
*/
|
|
||||||
public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider)
|
|
||||||
{
|
{
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.storeProvider = storeProvider;
|
this.storeProvider = storeProvider;
|
||||||
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -126,12 +119,17 @@ public class GitContext implements Closeable, RepositoryProvider
|
|||||||
storeProvider.get(repository).set(newConfig);
|
storeProvider.get(repository).set(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GitConfig getGlobalConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final File directory;
|
private final File directory;
|
||||||
private final Repository repository;
|
private final Repository repository;
|
||||||
private final GitRepositoryConfigStoreProvider storeProvider;
|
private final GitRepositoryConfigStoreProvider storeProvider;
|
||||||
|
private final GitConfig config;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private org.eclipse.jgit.lib.Repository gitRepository;
|
private org.eclipse.jgit.lib.Repository gitRepository;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class GitContextFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GitContext create(Repository repository) {
|
GitContext create(Repository repository) {
|
||||||
return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
|
return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider, handler.getConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,16 @@ import org.eclipse.jgit.api.Git;
|
|||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.attributes.FilterCommandRegistry;
|
import org.eclipse.jgit.attributes.FilterCommandRegistry;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.ConcurrentModificationException;
|
import sonia.scm.ConcurrentModificationException;
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
import sonia.scm.NoChangesMadeException;
|
import sonia.scm.NoChangesMadeException;
|
||||||
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
import sonia.scm.repository.GitWorkingCopyFactory;
|
import sonia.scm.repository.GitWorkingCopyFactory;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -49,21 +51,22 @@ import java.util.concurrent.locks.Lock;
|
|||||||
|
|
||||||
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
|
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GitModifyCommand.class);
|
|
||||||
private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5);
|
private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5);
|
||||||
|
|
||||||
private final GitWorkingCopyFactory workingCopyFactory;
|
private final GitWorkingCopyFactory workingCopyFactory;
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||||
|
private final GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
|
||||||
this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory);
|
this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory, gitRepositoryConfigStoreProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
|
||||||
super(context);
|
super(context);
|
||||||
this.workingCopyFactory = workingCopyFactory;
|
this.workingCopyFactory = workingCopyFactory;
|
||||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||||
|
this.gitRepositoryConfigStoreProvider = gitRepositoryConfigStoreProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -85,19 +88,49 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
|
|||||||
@Override
|
@Override
|
||||||
String run() throws IOException {
|
String run() throws IOException {
|
||||||
getClone().getRepository().getFullBranch();
|
getClone().getRepository().getFullBranch();
|
||||||
|
|
||||||
|
boolean initialCommit = getClone().getRepository().getRefDatabase().getRefs().isEmpty();
|
||||||
|
|
||||||
if (!StringUtils.isEmpty(request.getExpectedRevision())
|
if (!StringUtils.isEmpty(request.getExpectedRevision())
|
||||||
&& !request.getExpectedRevision().equals(getCurrentRevision().getName())) {
|
&& !request.getExpectedRevision().equals(getCurrentRevision().getName())) {
|
||||||
throw new ConcurrentModificationException("branch", request.getBranch() == null ? "default" : request.getBranch());
|
throw new ConcurrentModificationException(ContextEntry.ContextBuilder.entity("Branch", request.getBranch() == null ? "default" : request.getBranch()).in(repository).build());
|
||||||
}
|
}
|
||||||
for (ModifyCommandRequest.PartialRequest r : request.getRequests()) {
|
for (ModifyCommandRequest.PartialRequest r : request.getRequests()) {
|
||||||
r.execute(this);
|
r.execute(this);
|
||||||
}
|
}
|
||||||
failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()));
|
failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()));
|
||||||
Optional<RevCommit> revCommit = doCommit(request.getCommitMessage(), request.getAuthor(), request.isSign());
|
Optional<RevCommit> revCommit = doCommit(request.getCommitMessage(), request.getAuthor(), request.isSign());
|
||||||
|
|
||||||
|
if (initialCommit) {
|
||||||
|
handleBranchForInitialCommit();
|
||||||
|
}
|
||||||
|
|
||||||
push();
|
push();
|
||||||
return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name();
|
return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleBranchForInitialCommit() {
|
||||||
|
String branch = StringUtils.isNotBlank(request.getBranch()) ? request.getBranch() : context.getGlobalConfig().getDefaultBranch();
|
||||||
|
if (StringUtils.isNotBlank(branch)) {
|
||||||
|
try {
|
||||||
|
getClone().checkout().setName(branch).setCreateBranch(true).call();
|
||||||
|
setBranchInConfig(branch);
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(repository, "could not create default branch for initial commit", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBranchInConfig(String branch) {
|
||||||
|
ConfigurationStore<GitRepositoryConfig> store = gitRepositoryConfigStoreProvider
|
||||||
|
.get(repository);
|
||||||
|
GitRepositoryConfig gitRepositoryConfig = store
|
||||||
|
.getOptional()
|
||||||
|
.orElse(new GitRepositoryConfig());
|
||||||
|
gitRepositoryConfig.setDefaultBranch(branch);
|
||||||
|
store.set(gitRepositoryConfig);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFileToScm(String name, Path file) {
|
public void addFileToScm(String name, Path file) {
|
||||||
addToGitWithLfsSupport(name, file);
|
addToGitWithLfsSupport(name, file);
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ class GitWorkingCopyInitializer {
|
|||||||
Ref head = clone.exactRef(Constants.HEAD);
|
Ref head = clone.exactRef(Constants.HEAD);
|
||||||
|
|
||||||
if (head == null || !head.isSymbolic() || (initialBranch != null && !head.getTarget().getName().endsWith(initialBranch))) {
|
if (head == null || !head.isSymbolic() || (initialBranch != null && !head.getTarget().getName().endsWith(initialBranch))) {
|
||||||
throw notFound(entity("Branch", initialBranch).in(context.getRepository()));
|
if (clone.getRefDatabase().getRefs().isEmpty()) {
|
||||||
|
LOG.warn("could not initialize empty clone with given branch {}; this has to be handled later on", initialBranch);
|
||||||
|
} else {
|
||||||
|
throw notFound(entity("Branch", initialBranch).in(context.getRepository()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ParentAndClone<>(null, clone, target);
|
return new ParentAndClone<>(null, clone, target);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
@@ -40,36 +40,32 @@ import sonia.scm.plugin.Extension;
|
|||||||
*/
|
*/
|
||||||
@Extension
|
@Extension
|
||||||
public class GitUserAgentProvider implements UserAgentProvider {
|
public class GitUserAgentProvider implements UserAgentProvider {
|
||||||
|
|
||||||
private static final String PREFIX_JGIT = "jgit/";
|
private static final String PREFIX_JGIT = "jgit/";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final UserAgent JGIT = UserAgent.builder("JGit")
|
static final UserAgent JGIT = UserAgent.scmClient("JGit")
|
||||||
.browser(false)
|
|
||||||
.basicAuthenticationCharset(Charsets.UTF_8)
|
.basicAuthenticationCharset(Charsets.UTF_8)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final String PREFIX_REGULAR = "git/";
|
private static final String PREFIX_REGULAR = "git/";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final UserAgent GIT = UserAgent.builder("Git")
|
static final UserAgent GIT = UserAgent.scmClient("Git")
|
||||||
.browser(false)
|
|
||||||
.basicAuthenticationCharset(Charsets.UTF_8)
|
.basicAuthenticationCharset(Charsets.UTF_8)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final String PREFIX_LFS = "git-lfs/";
|
private static final String PREFIX_LFS = "git-lfs/";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final UserAgent GIT_LFS = UserAgent.builder("Git Lfs")
|
static final UserAgent GIT_LFS = UserAgent.scmClient("Git Lfs")
|
||||||
.browser(false)
|
|
||||||
.basicAuthenticationCharset(Charsets.UTF_8)
|
.basicAuthenticationCharset(Charsets.UTF_8)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final String SUFFIX_MSYSGIT = "msysgit";
|
private static final String SUFFIX_MSYSGIT = "msysgit";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final UserAgent MSYSGIT = UserAgent.builder("msysGit")
|
static final UserAgent MSYSGIT = UserAgent.scmClient("msysGit")
|
||||||
.browser(false)
|
|
||||||
.basicAuthenticationCharset(Charsets.UTF_8)
|
.basicAuthenticationCharset(Charsets.UTF_8)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -80,7 +76,7 @@ public class GitUserAgentProvider implements UserAgentProvider {
|
|||||||
@Override
|
@Override
|
||||||
public UserAgent parseUserAgent(String userAgentString) {
|
public UserAgent parseUserAgent(String userAgentString) {
|
||||||
String lowerUserAgent = toLower(userAgentString);
|
String lowerUserAgent = toLower(userAgentString);
|
||||||
|
|
||||||
if (isJGit(lowerUserAgent)) {
|
if (isJGit(lowerUserAgent)) {
|
||||||
return JGIT;
|
return JGIT;
|
||||||
} else if (isMsysGit(lowerUserAgent)) {
|
} else if (isMsysGit(lowerUserAgent)) {
|
||||||
@@ -93,23 +89,23 @@ public class GitUserAgentProvider implements UserAgentProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toLower(String value) {
|
private String toLower(String value) {
|
||||||
return Strings.nullToEmpty(value).toLowerCase(Locale.ENGLISH);
|
return Strings.nullToEmpty(value).toLowerCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isJGit(String userAgent) {
|
private boolean isJGit(String userAgent) {
|
||||||
return userAgent.startsWith(PREFIX_JGIT);
|
return userAgent.startsWith(PREFIX_JGIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMsysGit(String userAgent) {
|
private boolean isMsysGit(String userAgent) {
|
||||||
return userAgent.startsWith(PREFIX_REGULAR) && userAgent.contains(SUFFIX_MSYSGIT);
|
return userAgent.startsWith(PREFIX_REGULAR) && userAgent.contains(SUFFIX_MSYSGIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGitLFS(String userAgent) {
|
private boolean isGitLFS(String userAgent) {
|
||||||
return userAgent.startsWith(PREFIX_LFS);
|
return userAgent.startsWith(PREFIX_LFS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGit(String userAgent) {
|
private boolean isGit(String userAgent) {
|
||||||
return userAgent.startsWith(PREFIX_REGULAR);
|
return userAgent.startsWith(PREFIX_REGULAR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { Links } from "@scm-manager/ui-types";
|
import { Links } from "@scm-manager/ui-types";
|
||||||
import { InputField, Checkbox } from "@scm-manager/ui-components";
|
import { InputField, Checkbox, validation as validator } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Configuration = {
|
type Configuration = {
|
||||||
repositoryDirectory?: string;
|
repositoryDirectory?: string;
|
||||||
gcExpression?: string;
|
gcExpression?: string;
|
||||||
nonFastForwardDisallowed: boolean;
|
nonFastForwardDisallowed: boolean;
|
||||||
|
defaultBranch: string;
|
||||||
_links: Links;
|
_links: Links;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,8 +69,21 @@ class GitConfigurationForm extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onDefaultBranchChange = (value: string) => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
defaultBranch: value
|
||||||
|
},
|
||||||
|
() => this.props.onConfigurationChange(this.state, this.isValidDefaultBranch())
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
isValidDefaultBranch = () => {
|
||||||
|
return validator.isBranchValid(this.state.defaultBranch);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { gcExpression, nonFastForwardDisallowed } = this.state;
|
const { gcExpression, nonFastForwardDisallowed, defaultBranch } = this.state;
|
||||||
const { readOnly, t } = this.props;
|
const { readOnly, t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -90,6 +104,16 @@ class GitConfigurationForm extends React.Component<Props, State> {
|
|||||||
onChange={this.onNonFastForwardDisallowed}
|
onChange={this.onNonFastForwardDisallowed}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
/>
|
/>
|
||||||
|
<InputField
|
||||||
|
name="defaultBranch"
|
||||||
|
label={t("scm-git-plugin.config.defaultBranch")}
|
||||||
|
helpText={t("scm-git-plugin.config.defaultBranchHelpText")}
|
||||||
|
value={defaultBranch}
|
||||||
|
onChange={this.onDefaultBranchChange}
|
||||||
|
disabled={readOnly}
|
||||||
|
validationError={!this.isValidDefaultBranch()}
|
||||||
|
errorMessage={t("scm-git-plugin.config.defaultBranchValidationError")}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
"gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.",
|
"gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.",
|
||||||
"nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"",
|
"nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"",
|
||||||
"nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".",
|
"nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".",
|
||||||
|
"defaultBranch": "Default Branch",
|
||||||
|
"defaultBranchHelpText": "Dieser Name wird bei der Initialisierung neuer Git Repositories genutzt. Er hat keine weiteren Auswirkungen (insbesondere hat er keinen Einfluss auf den Branchnamen bei leeren Repositories).",
|
||||||
|
"defaultBranchValidationError": "Dies ist kein valider Branchname",
|
||||||
"disabled": "Deaktiviert",
|
"disabled": "Deaktiviert",
|
||||||
"disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin",
|
"disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin",
|
||||||
"submit": "Speichern"
|
"submit": "Speichern"
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
|
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
|
||||||
"nonFastForwardDisallowed": "Disallow Non Fast-Forward",
|
"nonFastForwardDisallowed": "Disallow Non Fast-Forward",
|
||||||
"nonFastForwardDisallowedHelpText": "Reject git pushes which are non fast-forward such as --force.",
|
"nonFastForwardDisallowedHelpText": "Reject git pushes which are non fast-forward such as --force.",
|
||||||
|
"defaultBranch": "Default Branch",
|
||||||
|
"defaultBranchHelpText": "This name will be used for the initialization of new git repositories. It has no effect otherwise (especially this cannot change the initial branch name for empty repositories).",
|
||||||
|
"defaultBranchValidationError": "This is not a valid branch name",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"disabledHelpText": "Enable or disable the Git plugin",
|
"disabledHelpText": "Enable or disable the Git plugin",
|
||||||
"submit": "Submit"
|
"submit": "Submit"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
@@ -261,7 +261,7 @@ public class GitConfigResourceTest {
|
|||||||
private MockHttpResponse put() throws URISyntaxException {
|
private MockHttpResponse put() throws URISyntaxException {
|
||||||
MockHttpRequest request = MockHttpRequest.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2)
|
MockHttpRequest request = MockHttpRequest.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2)
|
||||||
.contentType(GitVndMediaType.GIT_CONFIG)
|
.contentType(GitVndMediaType.GIT_CONFIG)
|
||||||
.content("{\"disabled\":true}".getBytes());
|
.content("{\"disabled\":true, \"defaultBranch\":\"main\"}".getBytes());
|
||||||
|
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|||||||
@@ -21,24 +21,17 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
|
||||||
import org.eclipse.jgit.transport.Transport;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitRepositoryConfig;
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
|
||||||
import sonia.scm.repository.api.HookContextFactory;
|
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
import static com.google.inject.util.Providers.of;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -69,7 +62,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()));
|
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()), new GitConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.repository.BrowserResult;
|
||||||
|
import sonia.scm.repository.FileObject;
|
||||||
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static sonia.scm.repository.spi.SyncAsyncExecutors.synchronousExecutor;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class GitBrowseCommand_BrokenSubmoduleTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||||
|
|
||||||
|
private GitBrowseCommand command;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createCommand() {
|
||||||
|
command = new GitBrowseCommand(createContext(), lfsBlobStoreFactory, synchronousExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBrowse() throws IOException {
|
||||||
|
BrowserResult result = command.getBrowserResult(new BrowseCommandRequest());
|
||||||
|
Collection<FileObject> children = result.getFile().getChildren();
|
||||||
|
|
||||||
|
List<String> subrepos = subRepositoriesOnly(children);
|
||||||
|
assertThat(subrepos).containsExactly(
|
||||||
|
"anonymous-access",
|
||||||
|
"hasselhoffme",
|
||||||
|
"recipes",
|
||||||
|
"scm-redmine-plugin"
|
||||||
|
);
|
||||||
|
|
||||||
|
List<String> directories = directoriesOnly(children);
|
||||||
|
assertThat(directories).containsExactly(
|
||||||
|
"dir",
|
||||||
|
"plugins"
|
||||||
|
);
|
||||||
|
|
||||||
|
List<String> files = filesOnly(children);
|
||||||
|
assertThat(files)
|
||||||
|
.containsExactly(
|
||||||
|
".gitmodules",
|
||||||
|
"README.md",
|
||||||
|
"test.txt"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBrowseRecursive() throws IOException {
|
||||||
|
BrowseCommandRequest request = new BrowseCommandRequest();
|
||||||
|
request.setRecursive(true);
|
||||||
|
BrowserResult result = command.getBrowserResult(request);
|
||||||
|
Collection<FileObject> children = result.getFile().getChildren();
|
||||||
|
FileObject fileObject = children.stream().filter(f -> "plugins".equals(f.getPath())).findFirst().get();
|
||||||
|
assertThat(fileObject.getChildren()).hasSize(3);
|
||||||
|
List<String> subrepos = subRepositoriesOnly(fileObject.getChildren());
|
||||||
|
assertThat(subrepos)
|
||||||
|
.containsExactly(
|
||||||
|
"plugins/scm-branchwp-plugin",
|
||||||
|
"plugins/scm-jira-plugin",
|
||||||
|
"plugins/statistic-plugin"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private List<String> filesOnly(Collection<FileObject> children) {
|
||||||
|
return children.stream().filter(f -> !f.isDirectory()).map(FileObject::getPath).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private List<String> directoriesOnly(Collection<FileObject> children) {
|
||||||
|
return children.stream()
|
||||||
|
.filter(FileObject::isDirectory)
|
||||||
|
.filter(f -> f.getSubRepository() == null)
|
||||||
|
.map(FileObject::getPath)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private List<String> subRepositoriesOnly(Collection<FileObject> children) {
|
||||||
|
return children.stream()
|
||||||
|
.filter(f -> f.getSubRepository() != null)
|
||||||
|
.map(FileObject::getPath)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getZippedRepositoryResource() {
|
||||||
|
return "sonia/scm/repository/spi/scm-git-broken-submodule-repo.zip";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public class GitIncomingCommandTest
|
|||||||
|
|
||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())));
|
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()));
|
||||||
PullCommandRequest req = new PullCommandRequest();
|
PullCommandRequest req = new PullCommandRequest();
|
||||||
req.setRemoteRepository(outgoingRepository);
|
req.setRemoteRepository(outgoingRepository);
|
||||||
pull.pull(req);
|
pull.pull(req);
|
||||||
@@ -177,7 +177,7 @@ public class GitIncomingCommandTest
|
|||||||
|
|
||||||
private GitIncomingCommand createCommand() {
|
private GitIncomingCommand createCommand() {
|
||||||
return new GitIncomingCommand(
|
return new GitIncomingCommand(
|
||||||
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
|
||||||
handler,
|
handler,
|
||||||
GitTestHelper.createConverterFactory()
|
GitTestHelper.createConverterFactory()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -42,8 +43,8 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, incomingRepository, null));
|
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()));
|
||||||
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, outgoingRepository, null));
|
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -106,11 +107,11 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pushOutgoingAndPullIncoming() throws IOException {
|
void pushOutgoingAndPullIncoming() throws IOException {
|
||||||
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null));
|
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()));
|
||||||
PushCommandRequest request = new PushCommandRequest();
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
request.setRemoteRepository(incomingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
cmd.push(request);
|
cmd.push(request);
|
||||||
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null));
|
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()));
|
||||||
PullCommandRequest pullRequest = new PullCommandRequest();
|
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||||
pullRequest.setRemoteRepository(incomingRepository);
|
pullRequest.setRemoteRepository(incomingRepository);
|
||||||
pullCommand.pull(pullRequest);
|
pullCommand.pull(pullRequest);
|
||||||
|
|||||||
@@ -24,61 +24,26 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.CanceledException;
|
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
|
||||||
import org.eclipse.jgit.lib.GpgSignature;
|
|
||||||
import org.eclipse.jgit.lib.GpgSigner;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.rules.TemporaryFolder;
|
|
||||||
import sonia.scm.AlreadyExistsException;
|
import sonia.scm.AlreadyExistsException;
|
||||||
import sonia.scm.BadRequestException;
|
import sonia.scm.BadRequestException;
|
||||||
import sonia.scm.ConcurrentModificationException;
|
import sonia.scm.ConcurrentModificationException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
|
||||||
import sonia.scm.security.PublicKey;
|
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
public class GitModifyCommandTest extends GitModifyCommandTestBase {
|
||||||
public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
||||||
@Rule
|
|
||||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setSigner() {
|
|
||||||
GpgSigner.setDefault(new GitTestHelper.SimpleGpgSigner());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateCommit() throws IOException, GitAPIException {
|
public void shouldCreateCommit() throws IOException, GitAPIException {
|
||||||
@@ -362,30 +327,4 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
|||||||
assertThat(lastCommit.getRawGpgSignature()).isNullOrEmpty();
|
assertThat(lastCommit.getRawGpgSignature()).isNullOrEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
|
||||||
try (Git git = new Git(createContext().open())) {
|
|
||||||
RevCommit lastCommit = getLastCommit(git);
|
|
||||||
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
|
||||||
RevCommit commit = walk.parseCommit(lastCommit);
|
|
||||||
ObjectId treeId = commit.getTree().getId();
|
|
||||||
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
|
||||||
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
|
||||||
return git.log().setMaxCount(1).call().iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GitModifyCommand createCommand() {
|
|
||||||
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
private interface TreeAssertions {
|
|
||||||
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||||
|
import org.eclipse.jgit.lib.GpgSigner;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.repository.GitTestHelper;
|
||||||
|
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||||
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static sonia.scm.repository.spi.GitRepositoryConfigStoreProviderTestUtil.createGitRepositoryConfigStoreProvider;
|
||||||
|
|
||||||
|
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
||||||
|
class GitModifyCommandTestBase extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
@Rule
|
||||||
|
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
||||||
|
@Rule
|
||||||
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setSigner() {
|
||||||
|
GpgSigner.setDefault(new GitTestHelper.SimpleGpgSigner());
|
||||||
|
}
|
||||||
|
|
||||||
|
RevCommit getLastCommit(Git git) throws GitAPIException, IOException {
|
||||||
|
return git.log().setMaxCount(1).call().iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
GitModifyCommand createCommand() {
|
||||||
|
return new GitModifyCommand(
|
||||||
|
createContext(),
|
||||||
|
new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())),
|
||||||
|
lfsBlobStoreFactory,
|
||||||
|
createGitRepositoryConfigStoreProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
RevCommit lastCommit = getLastCommit(git);
|
||||||
|
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
||||||
|
RevCommit commit = walk.parseCommit(lastCommit);
|
||||||
|
ObjectId treeId = commit.getTree().getId();
|
||||||
|
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
||||||
|
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface TreeAssertions {
|
||||||
|
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,22 +24,15 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
|
||||||
import sonia.scm.store.Blob;
|
import sonia.scm.store.Blob;
|
||||||
import sonia.scm.store.BlobStore;
|
import sonia.scm.store.BlobStore;
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -51,17 +44,7 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
public class GitModifyCommand_LFSTest extends GitModifyCommandTestBase {
|
||||||
public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
||||||
@Rule
|
|
||||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void registerFilter() {
|
public static void registerFilter() {
|
||||||
@@ -126,14 +109,6 @@ public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
|
|||||||
return command.execute(request);
|
return command.execute(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
|
||||||
return git.log().setMaxCount(1).call().iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GitModifyCommand createCommand() {
|
|
||||||
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getZippedRepositoryResource() {
|
protected String getZippedRepositoryResource() {
|
||||||
return "sonia/scm/repository/spi/scm-git-spi-lfs-test.zip";
|
return "sonia/scm/repository/spi/scm-git-spi-lfs-test.zip";
|
||||||
|
|||||||
@@ -24,42 +24,21 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
public class GitModifyCommand_withEmptyRepositoryTest extends GitModifyCommandTestBase {
|
||||||
public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommandTestBase {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
||||||
@Rule
|
|
||||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException {
|
public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException {
|
||||||
@@ -79,34 +58,65 @@ public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommand
|
|||||||
assertInTree(assertions);
|
assertInTree(assertions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCommitOnMasterByDefault() throws IOException, GitAPIException {
|
||||||
|
createContext().getGlobalConfig().setDefaultBranch("");
|
||||||
|
|
||||||
|
executeModifyCommand();
|
||||||
|
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
List<Ref> branches = git.branchList().call();
|
||||||
|
assertThat(branches).extracting("name").containsExactly("refs/heads/master");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCommitWithConfiguredDefaultBranch() throws IOException, GitAPIException {
|
||||||
|
createContext().getGlobalConfig().setDefaultBranch("main");
|
||||||
|
|
||||||
|
executeModifyCommand();
|
||||||
|
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
List<Ref> branches = git.branchList().call();
|
||||||
|
assertThat(branches).extracting("name").containsExactly("refs/heads/main");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCommitWithBranchFromRequestIfPresent() throws IOException, GitAPIException {
|
||||||
|
createContext().getGlobalConfig().setDefaultBranch("main");
|
||||||
|
|
||||||
|
ModifyCommandRequest request = createRequest();
|
||||||
|
request.setBranch("different");
|
||||||
|
createCommand().execute(request);
|
||||||
|
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
List<Ref> branches = git.branchList().call();
|
||||||
|
assertThat(branches).extracting("name").containsExactly("refs/heads/different");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getZippedRepositoryResource() {
|
protected String getZippedRepositoryResource() {
|
||||||
return "sonia/scm/repository/spi/scm-git-empty-repo.zip";
|
return "sonia/scm/repository/spi/scm-git-empty-repo.zip";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
@Override
|
||||||
try (Git git = new Git(createContext().open())) {
|
RevCommit getLastCommit(Git git) throws GitAPIException, IOException {
|
||||||
RevCommit lastCommit = getLastCommit(git);
|
return git.log().setMaxCount(1).all().call().iterator().next();
|
||||||
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
|
||||||
RevCommit commit = walk.parseCommit(lastCommit);
|
|
||||||
ObjectId treeId = commit.getTree().getId();
|
|
||||||
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
|
||||||
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
private void executeModifyCommand() throws IOException {
|
||||||
return git.log().setMaxCount(1).call().iterator().next();
|
createCommand().execute(createRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
private GitModifyCommand createCommand() {
|
private ModifyCommandRequest createRequest() throws IOException {
|
||||||
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory);
|
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
ModifyCommandRequest request = new ModifyCommandRequest();
|
||||||
private interface TreeAssertions {
|
request.setCommitMessage("initial commit");
|
||||||
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
request.addRequest(new ModifyCommandRequest.CreateFileRequest("new_file", newFile, false));
|
||||||
|
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPushCommand push = new GitPushCommand(handler,
|
GitPushCommand push = new GitPushCommand(handler,
|
||||||
new GitContext(outgoingDirectory, outgoingRepository, null)
|
new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig())
|
||||||
);
|
);
|
||||||
PushCommandRequest req = new PushCommandRequest();
|
PushCommandRequest req = new PushCommandRequest();
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
private GitOutgoingCommand createCommand()
|
private GitOutgoingCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitOutgoingCommand(
|
return new GitOutgoingCommand(
|
||||||
new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
|
||||||
handler,
|
handler,
|
||||||
GitTestHelper.createConverterFactory()
|
GitTestHelper.createConverterFactory()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ package sonia.scm.repository.spi;
|
|||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.api.PushResponse;
|
import sonia.scm.repository.api.PushResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -89,6 +90,6 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*/
|
*/
|
||||||
private GitPushCommand createCommand()
|
private GitPushCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null));
|
return new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
|
import sonia.scm.store.InMemoryConfigurationStore;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class GitRepositoryConfigStoreProviderTestUtil {
|
||||||
|
|
||||||
|
static GitRepositoryConfigStoreProvider createGitRepositoryConfigStoreProvider() {
|
||||||
|
GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider = mock(GitRepositoryConfigStoreProvider.class);
|
||||||
|
HashMap<String, ConfigurationStore<GitRepositoryConfig>> storeMap = new HashMap<>();
|
||||||
|
when(gitRepositoryConfigStoreProvider.get(any())).thenAnswer(invocation -> storeMap.computeIfAbsent(invocation.getArgument(0, Repository.class).getId(), id -> new InMemoryConfigurationStore<>()));
|
||||||
|
return gitRepositoryConfigStoreProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@scm-manager/scm-hg-plugin",
|
"name": "@scm-manager/scm-hg-plugin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.8.0-SNAPSHOT",
|
"version": "2.10.0-SNAPSHOT",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./src/main/js/index.ts",
|
"main": "./src/main/js/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -19,6 +19,6 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@scm-manager/prettier-config",
|
"prettier": "@scm-manager/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scm-manager/ui-plugins": "^2.8.0-SNAPSHOT"
|
"@scm-manager/ui-plugins": "^2.10.0-SNAPSHOT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-plugins</artifactId>
|
<artifactId>scm-plugins</artifactId>
|
||||||
<version>2.8.0-SNAPSHOT</version>
|
<version>2.10.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-hg-plugin</artifactId>
|
<artifactId>scm-hg-plugin</artifactId>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
@@ -54,11 +54,10 @@ import java.util.UUID;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class HgHookManager
|
public class HgHookManager {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
@SuppressWarnings("java:S1075") // this url is fixed
|
||||||
public static final String URL_HOOKPATH = "/hook/hg/";
|
private static final String URL_HOOKPATH = "/hook/hg/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for HgHookManager
|
* the logger for HgHookManager
|
||||||
@@ -191,64 +190,27 @@ public class HgHookManager
|
|||||||
return accessTokenBuilderFactory.create().build();
|
return accessTokenBuilderFactory.create().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
private void buildHookUrl(HttpServletRequest request) {
|
||||||
|
if (configuration.isForceBaseUrl()) {
|
||||||
/**
|
logger.debug("create hook url from configured base url because force base url is enabled");
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*/
|
|
||||||
private void buildHookUrl(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
if (configuration.isForceBaseUrl())
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(
|
|
||||||
"create hook url from configured base url because force base url is enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
hookUrl = createConfiguredUrl();
|
hookUrl = createConfiguredUrl();
|
||||||
|
if (!isUrlWorking(hookUrl)) {
|
||||||
if (!isUrlWorking(hookUrl))
|
|
||||||
{
|
|
||||||
disableHooks();
|
disableHooks();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
logger.debug("create hook url from request");
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("create hook url from request");
|
|
||||||
}
|
|
||||||
|
|
||||||
hookUrl = HttpUtil.getCompleteUrl(request, URL_HOOKPATH);
|
hookUrl = HttpUtil.getCompleteUrl(request, URL_HOOKPATH);
|
||||||
|
if (!isUrlWorking(hookUrl)) {
|
||||||
if (!isUrlWorking(hookUrl))
|
logger.warn("hook url {} from request does not work, try now localhost", hookUrl);
|
||||||
{
|
|
||||||
if (logger.isWarnEnabled())
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
"hook url {} from request does not work, try now localhost",
|
|
||||||
hookUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
hookUrl = createLocalUrl(request);
|
hookUrl = createLocalUrl(request);
|
||||||
|
if (!isUrlWorking(hookUrl)) {
|
||||||
if (!isUrlWorking(hookUrl))
|
logger.warn("localhost hook url {} does not work, try now from configured base url", hookUrl);
|
||||||
{
|
|
||||||
if (logger.isWarnEnabled())
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
"localhost hook url {} does not work, try now from configured base url",
|
|
||||||
hookUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
hookUrl = createConfiguredUrl();
|
hookUrl = createConfiguredUrl();
|
||||||
|
if (!isUrlWorking(hookUrl)) {
|
||||||
if (!isUrlWorking(hookUrl))
|
|
||||||
{
|
|
||||||
disableHooks();
|
disableHooks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +232,7 @@ public class HgHookManager
|
|||||||
configuration.getBaseUrl(),
|
configuration.getBaseUrl(),
|
||||||
"http://localhost:8080/scm"
|
"http://localhost:8080/scm"
|
||||||
)
|
)
|
||||||
).concat("/hook/hg/");
|
).concat(URL_HOOKPATH);
|
||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,11 +286,7 @@ public class HgHookManager
|
|||||||
{
|
{
|
||||||
request = httpServletRequestProvider.get();
|
request = httpServletRequestProvider.get();
|
||||||
}
|
}
|
||||||
catch (ProvisionException ex)
|
catch (ProvisionException | OutOfScopeException ex)
|
||||||
{
|
|
||||||
logger.debug("http servlet request is not available");
|
|
||||||
}
|
|
||||||
catch (OutOfScopeException ex)
|
|
||||||
{
|
{
|
||||||
logger.debug("http servlet request is not available");
|
logger.debug("http servlet request is not available");
|
||||||
}
|
}
|
||||||
@@ -358,6 +316,7 @@ public class HgHookManager
|
|||||||
.disableHostnameValidation(true)
|
.disableHostnameValidation(true)
|
||||||
.disableCertificateValidation(true)
|
.disableCertificateValidation(true)
|
||||||
.ignoreProxySettings(true)
|
.ignoreProxySettings(true)
|
||||||
|
.disableTracing()
|
||||||
.request()
|
.request()
|
||||||
.getStatus();
|
.getStatus();
|
||||||
//J+
|
//J+
|
||||||
|
|||||||