Merge branch 'develop' into feature/manage-tags
# Conflicts: # scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
25
CHANGELOG.md
@@ -6,11 +6,31 @@ 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
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Add tooltips to short links on repository overview ([#1441](https://github.com/scm-manager/scm-manager/pull/1441))
|
||||||
|
- Show the date of the last commit for branches in the frontend ([#1439](https://github.com/scm-manager/scm-manager/pull/1439))
|
||||||
|
- Unify and add description to key view across user settings ([#1440](https://github.com/scm-manager/scm-manager/pull/1440))
|
||||||
|
- Healthcheck for docker image ([#1428](https://github.com/scm-manager/scm-manager/issues/1428) and [#1454](https://github.com/scm-manager/scm-manager/issues/1454))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Send mercurial hook callbacks over separate tcp socket instead of http ([#1416](https://github.com/scm-manager/scm-manager/pull/1416))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Language detection of files with interpreter parameters e.g.: `#!/usr/bin/make -f` ([#1450](https://github.com/scm-manager/scm-manager/issues/1450))
|
||||||
|
|
||||||
|
## [2.10.1] - 2020-11-24
|
||||||
|
### Fixed
|
||||||
|
- Improved logging of failures during plugin installation ([#1442](https://github.com/scm-manager/scm-manager/pull/1442))
|
||||||
|
- Do not throw exception when plugin file does not exist on cancelled installation ([#1442](https://github.com/scm-manager/scm-manager/pull/1442))
|
||||||
|
|
||||||
|
## [2.10.0] - 2020-11-20
|
||||||
### Added
|
### Added
|
||||||
- Delete branches directly in the UI ([#1422](https://github.com/scm-manager/scm-manager/pull/1422))
|
- Delete branches directly in the UI ([#1422](https://github.com/scm-manager/scm-manager/pull/1422))
|
||||||
- Lookup command which provides further repository information ([#1415](https://github.com/scm-manager/scm-manager/pull/1415))
|
- Lookup command which provides further repository information ([#1415](https://github.com/scm-manager/scm-manager/pull/1415))
|
||||||
- Include messages from scm protocol in modification or merge errors ([#1420](https://github.com/scm-manager/scm-manager/pull/1420))
|
- Include messages from scm protocol in modification or merge errors ([#1420](https://github.com/scm-manager/scm-manager/pull/1420))
|
||||||
- Enhance trace api to accepted status codes ([#1430](https://github.com/scm-manager/scm-manager/pull/1430))
|
- Enhance trace api to accepted status codes ([#1430](https://github.com/scm-manager/scm-manager/pull/1430))
|
||||||
|
- Add examples to core resources to simplify usage of rest api ([#1434](https://github.com/scm-manager/scm-manager/pull/1434))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Missing close of hg diff command ([#1417](https://github.com/scm-manager/scm-manager/pull/1417))
|
- Missing close of hg diff command ([#1417](https://github.com/scm-manager/scm-manager/pull/1417))
|
||||||
@@ -414,3 +434,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
[2.6.3]: https://www.scm-manager.org/download/2.6.3
|
[2.6.3]: https://www.scm-manager.org/download/2.6.3
|
||||||
[2.7.0]: https://www.scm-manager.org/download/2.7.0
|
[2.7.0]: https://www.scm-manager.org/download/2.7.0
|
||||||
[2.7.1]: https://www.scm-manager.org/download/2.7.1
|
[2.7.1]: https://www.scm-manager.org/download/2.7.1
|
||||||
|
[2.8.0]: https://www.scm-manager.org/download/2.8.0
|
||||||
|
[2.9.0]: https://www.scm-manager.org/download/2.9.0
|
||||||
|
[2.9.1]: https://www.scm-manager.org/download/2.9.1
|
||||||
|
[2.10.0]: https://www.scm-manager.org/download/2.10.0
|
||||||
|
[2.10.1]: https://www.scm-manager.org/download/2.10.1
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 285 KiB |
@@ -13,7 +13,7 @@ eingegeben werden. Danach muss das neue Passwort zweimal eingegeben werden.
|
|||||||
|
|
||||||
## Öffentliche Schlüssel
|
## Öffentliche Schlüssel
|
||||||
|
|
||||||
Zum Prüfen von Signaturen für z. B. Commits können hier die entsprechenden öffentlichen Schlüssel hinterlegt werden.
|
Zum Prüfen von Signaturen für z. B. Commits können hier die entsprechenden öffentlichen GPG Schlüssel hinterlegt werden.
|
||||||
Zudem können hier die vom SCM-Manager erstellten Signaturschlüssel heruntergeladen werden.
|
Zudem können hier die vom SCM-Manager erstellten Signaturschlüssel heruntergeladen werden.
|
||||||
|
|
||||||
## API Schlüssel
|
## API Schlüssel
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 270 KiB |
@@ -4,8 +4,10 @@ subtitle: Branches
|
|||||||
---
|
---
|
||||||
### Übersicht
|
### Übersicht
|
||||||
Auf der Branches-Übersicht sind die bereits existierenden Branches aufgeführt. Bei einem Klick auf einen Branch wird man zur Detailseite des Branches weitergeleitet.
|
Auf der Branches-Übersicht sind die bereits existierenden Branches aufgeführt. Bei einem Klick auf einen Branch wird man zur Detailseite des Branches weitergeleitet.
|
||||||
|
Die Branches sind in zwei Listen aufgeteilt: Unter "Aktive Branches" sind Branches aufgelistet, deren letzter Commit
|
||||||
|
nicht 30 Tage älter als der Stand des Default-Branches ist. Alle älteren Branches sind in der Liste "Stale Branches" zu finden.
|
||||||
|
|
||||||
Der Tag "Default" gibt an welcher Branch aktuell, als Standard-Branch dieses Repository im SCM-Manager markiert ist. Der Standard-Branch wird immer zuerst angezeigt, wenn man das Repository im SCM-Manager öffnet.
|
Der Tag "Default" gibt an, welcher Branch aktuell als Standard-Branch dieses Repository im SCM-Manager markiert ist. Der Standard-Branch wird immer zuerst angezeigt, wenn man das Repository im SCM-Manager öffnet.
|
||||||
Alle Branches mit Ausnahme des Default Branches können über den Mülleimer-Icon unwiderruflich gelöscht werden.
|
Alle Branches mit Ausnahme des Default Branches können über den Mülleimer-Icon unwiderruflich gelöscht werden.
|
||||||
|
|
||||||
Über den "Branch erstellen"-Button gelangt man zum Formular, um neue Branches anzulegen.
|
Über den "Branch erstellen"-Button gelangt man zum Formular, um neue Branches anzulegen.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 275 KiB |
@@ -11,9 +11,9 @@ Here the password for the current account can be changed when it is a local acco
|
|||||||
external system). To authorize the change, the current password has to be put first. Then the new password has to be
|
external system). To authorize the change, the current password has to be put first. Then the new password has to be
|
||||||
entered twice.
|
entered twice.
|
||||||
|
|
||||||
## Öffentliche Schlüssel
|
## Public Keys
|
||||||
|
|
||||||
To check signatures for example for commits, public keys can be stored here. Additionally the keys created by
|
To check signatures (for example for commits), gpg public keys can be stored here. Additionally the keys created by
|
||||||
SCM-Manager can be accessed here, too.
|
SCM-Manager can be accessed here, too.
|
||||||
|
|
||||||
## API keys
|
## API keys
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 269 KiB |
@@ -4,6 +4,8 @@ subtitle: Branches
|
|||||||
---
|
---
|
||||||
### Overview
|
### Overview
|
||||||
The branches overview shows the branches that are already existing. By clicking on a branch, the details page of the branch is shown.
|
The branches overview shows the branches that are already existing. By clicking on a branch, the details page of the branch is shown.
|
||||||
|
Branches are split into two lists: Branches whose last commits are at most 30 days older than the head of the default
|
||||||
|
branch are listed in "Active Branches". The older ones can be found in "Stale Branches".
|
||||||
|
|
||||||
The tag "Default" shows which branch is currently set as the default branch of the repository in SCM-Manager. The default branch is always shown first when opening the repository in SCM-Manager.
|
The tag "Default" shows which branch is currently set as the default branch of the repository in SCM-Manager. The default branch is always shown first when opening the repository in SCM-Manager.
|
||||||
All branches except the default branch of the repository can be deleted by clicking on the trash bin icon.
|
All branches except the default branch of the repository can be deleted by clicking on the trash bin icon.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 263 KiB |
@@ -5,5 +5,5 @@
|
|||||||
],
|
],
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"version": "2.10.0-SNAPSHOT"
|
"version": "2.11.0-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|||||||
14
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.10.0-SNAPSHOT</version>
|
<version>2.11.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.
|
||||||
@@ -464,7 +464,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
<version>3.17.2</version>
|
<version>3.18.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -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.3.0</version>
|
<version>1.4.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -903,7 +903,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- test libraries -->
|
<!-- test libraries -->
|
||||||
<mockito.version>3.5.15</mockito.version>
|
<mockito.version>3.6.0</mockito.version>
|
||||||
<hamcrest.version>2.1</hamcrest.version>
|
<hamcrest.version>2.1</hamcrest.version>
|
||||||
<junit.version>5.7.0</junit.version>
|
<junit.version>5.7.0</junit.version>
|
||||||
|
|
||||||
@@ -919,13 +919,13 @@
|
|||||||
<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.6.Final</hibernate-validator.version>
|
<hibernate-validator.version>6.1.6.Final</hibernate-validator.version>
|
||||||
<bouncycastle.version>1.66</bouncycastle.version>
|
<bouncycastle.version>1.67</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.34.v20201102</jetty.version>
|
<jetty.version>9.4.35.v20201120</jetty.version>
|
||||||
<jetty.maven.version>9.4.34.v20201102</jetty.maven.version>
|
<jetty.maven.version>9.4.34.v20201102</jetty.maven.version>
|
||||||
|
|
||||||
<!-- security libraries -->
|
<!-- security libraries -->
|
||||||
@@ -937,7 +937,7 @@
|
|||||||
<svnkit.version>1.10.1-scm2</svnkit.version>
|
<svnkit.version>1.10.1-scm2</svnkit.version>
|
||||||
|
|
||||||
<!-- util libraries -->
|
<!-- util libraries -->
|
||||||
<guava.version>26.0-jre</guava.version>
|
<guava.version>30.0-jre</guava.version>
|
||||||
|
|
||||||
<!-- frontend -->
|
<!-- frontend -->
|
||||||
<nodejs.version>12.16.1</nodejs.version>
|
<nodejs.version>12.16.1</nodejs.version>
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-annotations</artifactId>
|
<artifactId>scm-annotations</artifactId>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-core</artifactId>
|
<artifactId>scm-core</artifactId>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -250,6 +250,12 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
71
scm-core/src/main/java/sonia/scm/TransactionId.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the current transaction.
|
||||||
|
* The transaction id is mainly used for logging and debugging.
|
||||||
|
*
|
||||||
|
* @since 2.10.0
|
||||||
|
*/
|
||||||
|
public final class TransactionId {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final String KEY = "transaction_id";
|
||||||
|
|
||||||
|
private TransactionId() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the given transaction id to the current thread.
|
||||||
|
*
|
||||||
|
* @param transactionId transaction id
|
||||||
|
*/
|
||||||
|
public static void set(String transactionId) {
|
||||||
|
MDC.put(KEY, transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an optional transaction id.
|
||||||
|
* If there is no transaction id bound to the thread, the method will return an empty optional.
|
||||||
|
*
|
||||||
|
* @return optional transaction id
|
||||||
|
*/
|
||||||
|
public static Optional<String> get() {
|
||||||
|
return Optional.ofNullable(MDC.get(KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a bound transaction id from the current thread.
|
||||||
|
*/
|
||||||
|
public static void clear() {
|
||||||
|
MDC.remove(KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- 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 sonia.scm.Validateable;
|
||||||
@@ -34,10 +32,9 @@ 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.Optional;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a branch in a repository.
|
* Represents a branch in a repository.
|
||||||
*
|
*
|
||||||
@@ -46,73 +43,100 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "branch")
|
@XmlRootElement(name = "branch")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public final class Branch implements Serializable, Validateable
|
public final class Branch implements Serializable, Validateable {
|
||||||
{
|
|
||||||
|
|
||||||
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
||||||
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
|
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 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);
|
public static final Pattern VALID_BRANCH_NAME_PATTERN = Pattern.compile(VALID_BRANCH_NAMES);
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final long serialVersionUID = -4602244691711222413L;
|
private static final long serialVersionUID = -4602244691711222413L;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private String name;
|
||||||
|
|
||||||
|
private String revision;
|
||||||
|
|
||||||
|
private boolean defaultBranch;
|
||||||
|
|
||||||
|
private Long lastCommitDate;
|
||||||
|
|
||||||
|
private boolean stale = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance of branch.
|
* Constructs a new instance of branch.
|
||||||
* This constructor should only be called from JAXB.
|
* This constructor should only be called from JAXB.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
Branch() {}
|
Branch() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new branch.
|
* Constructs a new branch.
|
||||||
*
|
*
|
||||||
|
* @param name name of the branch
|
||||||
|
* @param revision latest revision of the branch
|
||||||
|
* @param defaultBranch Whether this branch is the default branch for the repository
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link Branch#Branch(String, String, boolean, Long)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Branch(String name, String revision, boolean defaultBranch) {
|
||||||
|
this(name, revision, defaultBranch, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new branch.
|
||||||
*
|
*
|
||||||
* @param name name of the branch
|
* @param name name of the branch
|
||||||
* @param revision latest revision of the branch
|
* @param revision latest revision of the branch
|
||||||
|
* @param defaultBranch Whether this branch is the default branch for the repository
|
||||||
|
* @param lastCommitDate The date of the commit this branch points to (if computed). May be <code>null</code>
|
||||||
*/
|
*/
|
||||||
Branch(String name, String revision, boolean defaultBranch)
|
Branch(String name, String revision, boolean defaultBranch, Long lastCommitDate) {
|
||||||
{
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
this.defaultBranch = defaultBranch;
|
this.defaultBranch = defaultBranch;
|
||||||
|
this.lastCommitDate = lastCommitDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #normalBranch(String, String, Long)} instead to set the date of the last commit, too.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static Branch normalBranch(String name, String revision) {
|
public static Branch normalBranch(String name, String revision) {
|
||||||
return new Branch(name, revision, false);
|
return normalBranch(name, revision, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Branch normalBranch(String name, String revision, Long lastCommitDate) {
|
||||||
|
return new Branch(name, revision, false, lastCommitDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #defaultBranch(String, String, Long)} instead to set the date of the last commit, too.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static Branch defaultBranch(String name, String revision) {
|
public static Branch defaultBranch(String name, String revision) {
|
||||||
return new Branch(name, revision, true);
|
return defaultBranch(name, revision, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
public static Branch defaultBranch(String name, String revision, Long lastCommitDate) {
|
||||||
|
return new Branch(name, revision, true, lastCommitDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStale(boolean stale) {
|
||||||
|
this.stale = stale;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return VALID_BRANCH_NAME_PATTERN.matcher(name).matches();
|
return VALID_BRANCH_NAME_PATTERN.matcher(name).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,48 +144,31 @@ public final class Branch implements Serializable, Validateable
|
|||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(revision, other.revision)
|
&& Objects.equal(revision, other.revision)
|
||||||
&& Objects.equal(defaultBranch, other.defaultBranch);
|
&& Objects.equal(defaultBranch, other.defaultBranch)
|
||||||
|
&& Objects.equal(lastCommitDate, other.lastCommitDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode() {
|
||||||
{
|
|
||||||
return Objects.hashCode(name, revision);
|
return Objects.hashCode(name, revision);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString() {
|
||||||
{
|
|
||||||
//J-
|
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("name", name)
|
.add("name", name)
|
||||||
.add("revision", revision)
|
.add("revision", revision)
|
||||||
|
.add("defaultBranch", defaultBranch)
|
||||||
|
.add("lastCommitDate", lastCommitDate)
|
||||||
.toString();
|
.toString();
|
||||||
//J+
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the branch
|
* Returns the name of the branch
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return name of the branch
|
* @return name of the branch
|
||||||
*/
|
*/
|
||||||
public String getName()
|
public String getName() {
|
||||||
{
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,22 +177,27 @@ public final class Branch implements Serializable, Validateable
|
|||||||
*
|
*
|
||||||
* @return latest revision of branch
|
* @return latest revision of branch
|
||||||
*/
|
*/
|
||||||
public String getRevision()
|
public String getRevision() {
|
||||||
{
|
|
||||||
return revision;
|
return revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag whether this branch is configured as the default branch.
|
||||||
|
*/
|
||||||
public boolean isDefaultBranch() {
|
public boolean isDefaultBranch() {
|
||||||
return defaultBranch;
|
return defaultBranch;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
/**
|
||||||
|
* The date of the commit this branch points to, if this was computed (can be empty).
|
||||||
|
*
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
public Optional<Long> getLastCommitDate() {
|
||||||
|
return Optional.ofNullable(lastCommitDate);
|
||||||
|
}
|
||||||
|
|
||||||
/** name of the branch */
|
public boolean isStale() {
|
||||||
private String name;
|
return stale;
|
||||||
|
}
|
||||||
/** Field description */
|
|
||||||
private String revision;
|
|
||||||
|
|
||||||
private boolean defaultBranch;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.api;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.spi.BranchStaleComputer;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import static java.time.Instant.ofEpochMilli;
|
||||||
|
|
||||||
|
public class BranchXDaysOlderThanDefaultStaleComputer implements BranchStaleComputer {
|
||||||
|
|
||||||
|
public static final int DEFAULT_AMOUNT_OF_DAYS = 30;
|
||||||
|
|
||||||
|
private final int amountOfDays;
|
||||||
|
|
||||||
|
public BranchXDaysOlderThanDefaultStaleComputer() {
|
||||||
|
this(DEFAULT_AMOUNT_OF_DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BranchXDaysOlderThanDefaultStaleComputer(int amountOfDays) {
|
||||||
|
this.amountOfDays = amountOfDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("java:S3655") // we check "isPresent" for both dates, but due to the third check sonar does not get it
|
||||||
|
public boolean computeStale(Branch branch, StaleContext context) {
|
||||||
|
Branch defaultBranch = context.getDefaultBranch();
|
||||||
|
if (shouldCompute(branch, defaultBranch)) {
|
||||||
|
Instant defaultCommitDate = ofEpochMilli(defaultBranch.getLastCommitDate().get());
|
||||||
|
Instant thisCommitDate = ofEpochMilli(branch.getLastCommitDate().get());
|
||||||
|
return thisCommitDate.plus(amountOfDays, ChronoUnit.DAYS).isBefore(defaultCommitDate);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldCompute(Branch branch, Branch defaultBranch) {
|
||||||
|
return !branch.isDefaultBranch() && branch.getLastCommitDate().isPresent() && defaultBranch.getLastCommitDate().isPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -165,7 +165,7 @@ public final class BranchesCommandBuilder
|
|||||||
private Branches getBranchesFromCommand()
|
private Branches getBranchesFromCommand()
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
return new Branches(branchesCommand.getBranches());
|
return new Branches(branchesCommand.getBranchesWithStaleFlags(new BranchXDaysOlderThanDefaultStaleComputer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
//~--- inner classes --------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 lombok.Data;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
|
||||||
|
public interface BranchStaleComputer {
|
||||||
|
|
||||||
|
boolean computeStale(Branch branch, StaleContext context);
|
||||||
|
|
||||||
|
@Data
|
||||||
|
class StaleContext {
|
||||||
|
private Branch defaultBranch;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,30 +24,49 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 1.18
|
* @since 1.18
|
||||||
*/
|
*/
|
||||||
public interface BranchesCommand
|
public interface BranchesCommand {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
List<Branch> getBranches() throws IOException;
|
List<Branch> getBranches() throws IOException;
|
||||||
|
|
||||||
|
default List<Branch> getBranchesWithStaleFlags(BranchStaleComputer computer) throws IOException {
|
||||||
|
List<Branch> branches = getBranches();
|
||||||
|
new StaleProcessor(computer, branches).process();
|
||||||
|
return branches;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class StaleProcessor {
|
||||||
|
|
||||||
|
private final BranchStaleComputer computer;
|
||||||
|
private final List<Branch> branches;
|
||||||
|
|
||||||
|
private StaleProcessor(BranchStaleComputer computer, List<Branch> branches) {
|
||||||
|
this.computer = computer;
|
||||||
|
this.branches = branches;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
Optional<Branch> defaultBranch = branches.stream()
|
||||||
|
.filter(Branch::isDefaultBranch)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
defaultBranch.ifPresent(this::process);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process(Branch defaultBranch) {
|
||||||
|
BranchStaleComputer.StaleContext staleContext = new BranchStaleComputer.StaleContext();
|
||||||
|
staleContext.setDefaultBranch(defaultBranch);
|
||||||
|
|
||||||
|
branches.forEach(branch -> branch.setStale(computer.computeStale(branch, staleContext)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
scm-core/src/test/java/sonia/scm/TransactionIdTest.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class TransactionIdTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSetGetAndClear() {
|
||||||
|
TransactionId.set("42");
|
||||||
|
|
||||||
|
assertThat(TransactionId.get()).contains("42");
|
||||||
|
TransactionId.clear();
|
||||||
|
assertThat(TransactionId.get()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.api;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.spi.BranchStaleComputer;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import static java.time.Instant.now;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static sonia.scm.repository.Branch.defaultBranch;
|
||||||
|
import static sonia.scm.repository.Branch.normalBranch;
|
||||||
|
|
||||||
|
class BranchXDaysOlderThanDefaultStaleComputerTest {
|
||||||
|
|
||||||
|
Instant now = now();
|
||||||
|
|
||||||
|
BranchXDaysOlderThanDefaultStaleComputer computer = new BranchXDaysOlderThanDefaultStaleComputer(30);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTagOldBranchAsStale() {
|
||||||
|
long staleTime =
|
||||||
|
now
|
||||||
|
.minus(30, ChronoUnit.DAYS)
|
||||||
|
.minus(1, ChronoUnit.MINUTES)
|
||||||
|
.toEpochMilli();
|
||||||
|
|
||||||
|
Branch branch = normalBranch("hog", "42", staleTime);
|
||||||
|
boolean stale = computer.computeStale(branch, createStaleContext());
|
||||||
|
|
||||||
|
assertThat(stale).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotTagNotSoOldBranchAsStale() {
|
||||||
|
long activeTime =
|
||||||
|
now
|
||||||
|
.minus(30, ChronoUnit.DAYS)
|
||||||
|
.plus(1, ChronoUnit.MINUTES)
|
||||||
|
.toEpochMilli();
|
||||||
|
|
||||||
|
Branch branch = normalBranch("hog", "42", activeTime);
|
||||||
|
boolean stale = computer.computeStale(branch, createStaleContext());
|
||||||
|
|
||||||
|
assertThat(stale).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotTagDefaultBranchAsStale() {
|
||||||
|
long staleTime =
|
||||||
|
now
|
||||||
|
.minus(30, ChronoUnit.DAYS)
|
||||||
|
.minus(1, ChronoUnit.MINUTES)
|
||||||
|
.toEpochMilli();
|
||||||
|
|
||||||
|
Branch branch = defaultBranch("hog", "42", staleTime);
|
||||||
|
boolean stale = computer.computeStale(branch, createStaleContext());
|
||||||
|
|
||||||
|
assertThat(stale).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
BranchStaleComputer.StaleContext createStaleContext() {
|
||||||
|
BranchStaleComputer.StaleContext staleContext = new BranchStaleComputer.StaleContext();
|
||||||
|
staleContext.setDefaultBranch(defaultBranch("default", "23", now.toEpochMilli()));
|
||||||
|
return staleContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.assertj.core.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.api.BranchXDaysOlderThanDefaultStaleComputer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.time.Instant.now;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
class BranchesCommandTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMarkEachBranchDependingOnDefaultBranch() throws IOException {
|
||||||
|
Instant now = now();
|
||||||
|
long staleTime =
|
||||||
|
now
|
||||||
|
.minus(30, ChronoUnit.DAYS)
|
||||||
|
.minus(1, ChronoUnit.MINUTES)
|
||||||
|
.toEpochMilli();
|
||||||
|
long activeTime =
|
||||||
|
now
|
||||||
|
.minus(30, ChronoUnit.DAYS)
|
||||||
|
.plus(1, ChronoUnit.MINUTES)
|
||||||
|
.toEpochMilli();
|
||||||
|
|
||||||
|
List<Branch> branches = asList(
|
||||||
|
Branch.normalBranch("arthur", "42", staleTime),
|
||||||
|
Branch.normalBranch("marvin", "42", staleTime),
|
||||||
|
Branch.defaultBranch("hog", "42", now.toEpochMilli()),
|
||||||
|
Branch.normalBranch("trillian", "42", activeTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Branch> branchesWithStaleFlags = new BranchesCommand() {
|
||||||
|
@Override
|
||||||
|
public List<Branch> getBranches() {
|
||||||
|
return branches;
|
||||||
|
}
|
||||||
|
}.getBranchesWithStaleFlags(new BranchXDaysOlderThanDefaultStaleComputer());
|
||||||
|
|
||||||
|
Assertions.assertThat(branchesWithStaleFlags)
|
||||||
|
.extracting("stale")
|
||||||
|
.containsExactly(true, true, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,11 +31,11 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-dao-xml</artifactId>
|
<artifactId>scm-dao-xml</artifactId>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>deb</artifactId>
|
<artifactId>deb</artifactId>
|
||||||
<packaging>deb</packaging>
|
<packaging>deb</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -41,4 +41,9 @@ VOLUME ["${SCM_HOME}", "${CACHE_DIR}"]
|
|||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER scm
|
USER scm
|
||||||
|
|
||||||
|
# we us a high relative high start period,
|
||||||
|
# because the start time depends on the number of installed plugins
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/scm/api/v2 || exit 1
|
||||||
|
|
||||||
ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ]
|
ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ]
|
||||||
|
|||||||
@@ -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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>docker</artifactId>
|
<artifactId>docker</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>helm</artifactId>
|
<artifactId>helm</artifactId>
|
||||||
<packaging>helm</packaging>
|
<packaging>helm</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>release-yaml</artifactId>
|
<artifactId>release-yaml</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>rpm</artifactId>
|
<artifactId>rpm</artifactId>
|
||||||
<packaging>rpm</packaging>
|
<packaging>rpm</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>unix</artifactId>
|
<artifactId>unix</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>windows</artifactId>
|
<artifactId>windows</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm</artifactId>
|
<artifactId>scm</artifactId>
|
||||||
<version>2.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.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.10.0-SNAPSHOT",
|
"version": "2.11.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.10.0-SNAPSHOT"
|
"@scm-manager/ui-plugins": "^2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-git-plugin</artifactId>
|
<artifactId>scm-git-plugin</artifactId>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class GitConfigDto extends HalRepresentation {
|
public class GitConfigDto extends HalRepresentation implements UpdateGitConfigDto {
|
||||||
|
|
||||||
private boolean disabled = false;
|
private boolean disabled = false;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ package sonia.scm.api.v2.resources;
|
|||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import sonia.scm.config.ConfigurationPermissions;
|
import sonia.scm.config.ConfigurationPermissions;
|
||||||
@@ -116,7 +118,23 @@ public class GitConfigResource {
|
|||||||
@PUT
|
@PUT
|
||||||
@Path("")
|
@Path("")
|
||||||
@Consumes(GitVndMediaType.GIT_CONFIG)
|
@Consumes(GitVndMediaType.GIT_CONFIG)
|
||||||
@Operation(summary = "Modify git configuration", description = "Modifies the global git configuration.", tags = "Git", operationId = "git_put_config")
|
@Operation(
|
||||||
|
summary = "Modify git configuration",
|
||||||
|
description = "Modifies the global git configuration.",
|
||||||
|
tags = "Git",
|
||||||
|
operationId = "git_put_config",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
content = @Content(
|
||||||
|
mediaType = GitVndMediaType.GIT_CONFIG,
|
||||||
|
schema = @Schema(implementation = UpdateGitConfigDto.class),
|
||||||
|
examples = @ExampleObject(
|
||||||
|
name = "Overwrites current configuration with this one.",
|
||||||
|
value = "{\n \"disabled\":false,\n \"gcExpression\":null,\n \"nonFastForwardDisallowed\":false,\n \"defaultBranch\":\"main\"\n}",
|
||||||
|
summary = "Simple update configuration"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@ApiResponse(responseCode = "204", description = "update success")
|
@ApiResponse(responseCode = "204", description = "update success")
|
||||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||||
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:git\" privilege")
|
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:git\" privilege")
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@SuppressWarnings("squid:S2160") // there is no proper semantic for equals on this dto
|
@SuppressWarnings("squid:S2160") // there is no proper semantic for equals on this dto
|
||||||
public class GitRepositoryConfigDto extends HalRepresentation {
|
public class GitRepositoryConfigDto extends HalRepresentation implements UpdateGitRepositoryConfigDto {
|
||||||
|
|
||||||
private String defaultBranch;
|
private String defaultBranch;
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import lombok.Getter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.GitRepositoryConfig;
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
@@ -106,7 +109,22 @@ public class GitRepositoryConfigResource {
|
|||||||
@PUT
|
@PUT
|
||||||
@Path("/")
|
@Path("/")
|
||||||
@Consumes(GitVndMediaType.GIT_REPOSITORY_CONFIG)
|
@Consumes(GitVndMediaType.GIT_REPOSITORY_CONFIG)
|
||||||
@Operation(summary = "Modifies git repository configuration", description = "Modifies the repository related git configuration.", tags = "Git")
|
@Operation(
|
||||||
|
summary = "Modifies git repository configuration",
|
||||||
|
description = "Modifies the repository related git configuration.",
|
||||||
|
tags = "Git",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
content = @Content(
|
||||||
|
mediaType = GitVndMediaType.GIT_REPOSITORY_CONFIG,
|
||||||
|
schema = @Schema(implementation = UpdateGitRepositoryConfigDto.class),
|
||||||
|
examples = @ExampleObject(
|
||||||
|
name = "Overwrites current configuration with this one.",
|
||||||
|
value = "{\n \"defaultBranch\":\"main\"\n}",
|
||||||
|
summary = "Simple update configuration"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "204",
|
responseCode = "204",
|
||||||
description = "update success"
|
description = "update success"
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.api.v2.resources;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
interface UpdateGitConfigDto {
|
||||||
|
|
||||||
|
boolean isDisabled();
|
||||||
|
|
||||||
|
String getGcExpression();
|
||||||
|
|
||||||
|
boolean isNonFastForwardDisallowed();
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
@Length(min = 1, max = 100)
|
||||||
|
@Pattern(regexp = VALID_BRANCH_NAMES)
|
||||||
|
String getDefaultBranch();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.api.v2.resources;
|
||||||
|
|
||||||
|
interface UpdateGitRepositoryConfigDto {
|
||||||
|
String getDefaultBranch();
|
||||||
|
}
|
||||||
@@ -24,14 +24,14 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
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.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
@@ -44,12 +44,9 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
import static sonia.scm.repository.GitUtil.getCommit;
|
||||||
|
import static sonia.scm.repository.GitUtil.getCommitTime;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class GitBranchesCommand extends AbstractGitCommand implements BranchesCommand {
|
public class GitBranchesCommand extends AbstractGitCommand implements BranchesCommand {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GitBranchesCommand.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GitBranchesCommand.class);
|
||||||
@@ -60,23 +57,22 @@ public class GitBranchesCommand extends AbstractGitCommand implements BranchesCo
|
|||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Branch> getBranches() throws IOException {
|
public List<Branch> getBranches() throws IOException {
|
||||||
Git git = createGit();
|
Git git = createGit();
|
||||||
|
|
||||||
String defaultBranchName = determineDefaultBranchName(git);
|
String defaultBranchName = determineDefaultBranchName(git);
|
||||||
|
|
||||||
try {
|
Repository repository = git.getRepository();
|
||||||
|
try (RevWalk refWalk = new RevWalk(repository)) {
|
||||||
return git
|
return git
|
||||||
.branchList()
|
.branchList()
|
||||||
.call()
|
.call()
|
||||||
.stream()
|
.stream()
|
||||||
.map(ref -> createBranchObject(defaultBranchName, ref))
|
.map(ref -> createBranchObject(repository, refWalk, defaultBranchName, ref))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} catch (GitAPIException ex) {
|
} catch (GitAPIException ex) {
|
||||||
throw new InternalRepositoryException(repository, "could not read branches", ex);
|
throw new InternalRepositoryException(this.repository, "could not read branches", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,21 +82,31 @@ public class GitBranchesCommand extends AbstractGitCommand implements BranchesCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Branch createBranchObject(String defaultBranchName, Ref ref) {
|
private Branch createBranchObject(Repository repository, RevWalk refWalk, String defaultBranchName, Ref ref) {
|
||||||
String branchName = GitUtil.getBranch(ref);
|
String branchName = GitUtil.getBranch(ref);
|
||||||
|
|
||||||
if (branchName == null) {
|
if (branchName == null) {
|
||||||
LOG.warn("could not determine branch name for branch name {} at revision {}", ref.getName(), ref.getObjectId());
|
LOG.warn("could not determine branch name for branch name {} at revision {}", ref.getName(), ref.getObjectId());
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
Long lastCommitDate = getCommitDate(repository, refWalk, branchName, ref);
|
||||||
if (branchName.equals(defaultBranchName)) {
|
if (branchName.equals(defaultBranchName)) {
|
||||||
return Branch.defaultBranch(branchName, GitUtil.getId(ref.getObjectId()));
|
return Branch.defaultBranch(branchName, GitUtil.getId(ref.getObjectId()), lastCommitDate);
|
||||||
} else {
|
} else {
|
||||||
return Branch.normalBranch(branchName, GitUtil.getId(ref.getObjectId()));
|
return Branch.normalBranch(branchName, GitUtil.getId(ref.getObjectId()), lastCommitDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long getCommitDate(Repository repository, RevWalk refWalk, String branchName, Ref ref) {
|
||||||
|
try {
|
||||||
|
return getCommitTime(getCommit(repository, refWalk, ref));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.info("failed to read commit date of branch {} with revision {}", branchName, ref.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String determineDefaultBranchName(Git git) {
|
private String determineDefaultBranchName(Git git) {
|
||||||
String defaultBranchName = context.getConfig().getDefaultBranch();
|
String defaultBranchName = context.getConfig().getDefaultBranch();
|
||||||
if (Strings.isNullOrEmpty(defaultBranchName)) {
|
if (Strings.isNullOrEmpty(defaultBranchName)) {
|
||||||
|
|||||||
@@ -24,117 +24,26 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.junit.Test;
|
||||||
import org.eclipse.jgit.api.ListBranchCommand;
|
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
|
||||||
import org.eclipse.jgit.lib.Ref;
|
|
||||||
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.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
import sonia.scm.repository.GitRepositoryConfig;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Optional.of;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.lenient;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
public class GitBranchesCommandTest extends AbstractGitCommandTestBase {
|
||||||
class GitBranchesCommandTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
GitContext context;
|
|
||||||
@Mock
|
|
||||||
Git git;
|
|
||||||
@Mock
|
|
||||||
ListBranchCommand listBranchCommand;
|
|
||||||
@Mock
|
|
||||||
GitRepositoryConfig gitRepositoryConfig;
|
|
||||||
|
|
||||||
GitBranchesCommand branchesCommand;
|
|
||||||
private Ref master;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void initContext() {
|
|
||||||
when(context.getConfig()).thenReturn(gitRepositoryConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void initCommand() {
|
|
||||||
master = createRef("master", "0000");
|
|
||||||
branchesCommand = new GitBranchesCommand(context) {
|
|
||||||
@Override
|
|
||||||
Git createGit() {
|
|
||||||
return git;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Optional<Ref> getRepositoryHeadRef(Git git) {
|
|
||||||
return of(master);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
when(git.branchList()).thenReturn(listBranchCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateEmptyListWithoutBranches() throws IOException, GitAPIException {
|
public void shouldReadBranches() throws IOException {
|
||||||
when(listBranchCommand.call()).thenReturn(emptyList());
|
GitBranchesCommand branchesCommand = new GitBranchesCommand(createContext());
|
||||||
|
|
||||||
List<Branch> branches = branchesCommand.getBranches();
|
List<Branch> branches = branchesCommand.getBranches();
|
||||||
|
|
||||||
assertThat(branches).isEmpty();
|
assertThat(branches).contains(
|
||||||
}
|
Branch.defaultBranch("master", "fcd0ef1831e4002ac43ea539f4094334c79ea9ec", 1339428655000L),
|
||||||
|
Branch.normalBranch("mergeable", "91b99de908fcd04772798a31c308a64aea1a5523", 1541586052000L),
|
||||||
@Test
|
Branch.normalBranch("rename", "383b954b27e052db6880d57f1c860dc208795247", 1589203061000L)
|
||||||
void shouldMapNormalBranch() throws IOException, GitAPIException {
|
|
||||||
Ref branch = createRef("branch", "1337");
|
|
||||||
when(listBranchCommand.call()).thenReturn(asList(branch));
|
|
||||||
|
|
||||||
List<Branch> branches = branchesCommand.getBranches();
|
|
||||||
|
|
||||||
assertThat(branches).containsExactly(Branch.normalBranch("branch", "1337"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldMarkMasterBranchWithMasterFromConfig() throws IOException, GitAPIException {
|
|
||||||
Ref branch = createRef("branch", "1337");
|
|
||||||
when(listBranchCommand.call()).thenReturn(asList(branch));
|
|
||||||
when(gitRepositoryConfig.getDefaultBranch()).thenReturn("branch");
|
|
||||||
|
|
||||||
List<Branch> branches = branchesCommand.getBranches();
|
|
||||||
|
|
||||||
assertThat(branches).containsExactlyInAnyOrder(Branch.defaultBranch("branch", "1337"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldMarkMasterBranchWithMasterFromHead() throws IOException, GitAPIException {
|
|
||||||
Ref branch = createRef("branch", "1337");
|
|
||||||
when(listBranchCommand.call()).thenReturn(asList(branch, master));
|
|
||||||
|
|
||||||
List<Branch> branches = branchesCommand.getBranches();
|
|
||||||
|
|
||||||
assertThat(branches).containsExactlyInAnyOrder(
|
|
||||||
Branch.normalBranch("branch", "1337"),
|
|
||||||
Branch.defaultBranch("master", "0000")
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Ref createRef(String branchName, String revision) {
|
|
||||||
Ref ref = mock(Ref.class);
|
|
||||||
lenient().when(ref.getName()).thenReturn("refs/heads/" + branchName);
|
|
||||||
ObjectId objectId = mock(ObjectId.class);
|
|
||||||
lenient().when(objectId.name()).thenReturn(revision);
|
|
||||||
lenient().when(ref.getObjectId()).thenReturn(objectId);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@scm-manager/scm-hg-plugin",
|
"name": "@scm-manager/scm-hg-plugin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.10.0-SNAPSHOT",
|
"version": "2.11.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.10.0-SNAPSHOT"
|
"@scm-manager/ui-plugins": "^2.11.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.10.0-SNAPSHOT</version>
|
<version>2.11.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>scm-hg-plugin</artifactId>
|
<artifactId>scm-hg-plugin</artifactId>
|
||||||
@@ -44,15 +44,29 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aragost.javahg</groupId>
|
<groupId>com.aragost.javahg</groupId>
|
||||||
<artifactId>javahg</artifactId>
|
<artifactId>javahg</artifactId>
|
||||||
<version>0.15-scm1</version>
|
<version>0.16</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-nop</artifactId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import sonia.scm.config.ConfigurationPermissions;
|
import sonia.scm.config.ConfigurationPermissions;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
@@ -83,7 +85,22 @@ public class HgConfigAutoConfigurationResource {
|
|||||||
@PUT
|
@PUT
|
||||||
@Path("")
|
@Path("")
|
||||||
@Consumes(HgVndMediaType.CONFIG)
|
@Consumes(HgVndMediaType.CONFIG)
|
||||||
@Operation(summary = "Modifies hg configuration and installs hg binary", description = "Modifies the mercurial config and installs the mercurial binary.", tags = "Mercurial")
|
@Operation(
|
||||||
|
summary = "Modifies hg configuration and installs hg binary",
|
||||||
|
description = "Modifies the mercurial config and installs the mercurial binary.",
|
||||||
|
tags = "Mercurial",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
content = @Content(
|
||||||
|
mediaType = HgVndMediaType.CONFIG,
|
||||||
|
schema = @Schema(implementation = UpdateHgConfigDto.class),
|
||||||
|
examples = @ExampleObject(
|
||||||
|
name = "Overwrites current configuration with this one and installs the mercurial binary.",
|
||||||
|
value = "{\n \"disabled\":false,\n \"hgBinary\":\"hg\",\n \"pythonBinary\":\"python\",\n \"pythonPath\":\"\",\n \"encoding\":\"UTF-8\",\n \"useOptimizedBytecode\":false,\n \"showRevisionInId\":false,\n \"disableHookSSLValidation\":false,\n \"enableHttpPostArgs\":false\n}",
|
||||||
|
summary = "Simple update configuration and installs binary"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "204",
|
responseCode = "204",
|
||||||
description = "update success"
|
description = "update success"
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class HgConfigDto extends HalRepresentation {
|
@NoArgsConstructor
|
||||||
|
@SuppressWarnings("java:S2160") // we don't need equals for dto
|
||||||
|
public class HgConfigDto extends HalRepresentation implements UpdateHgConfigDto {
|
||||||
|
|
||||||
|
|
||||||
private boolean disabled;
|
private boolean disabled;
|
||||||
|
|
||||||
@@ -44,7 +46,6 @@ public class HgConfigDto extends HalRepresentation {
|
|||||||
private boolean useOptimizedBytecode;
|
private boolean useOptimizedBytecode;
|
||||||
private boolean showRevisionInId;
|
private boolean showRevisionInId;
|
||||||
private boolean enableHttpPostArgs;
|
private boolean enableHttpPostArgs;
|
||||||
private boolean disableHookSSLValidation;
|
|
||||||
|
|
||||||
@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
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ package sonia.scm.api.v2.resources;
|
|||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import sonia.scm.config.ConfigurationPermissions;
|
import sonia.scm.config.ConfigurationPermissions;
|
||||||
@@ -121,7 +123,23 @@ public class HgConfigResource {
|
|||||||
@PUT
|
@PUT
|
||||||
@Path("")
|
@Path("")
|
||||||
@Consumes(HgVndMediaType.CONFIG)
|
@Consumes(HgVndMediaType.CONFIG)
|
||||||
@Operation(summary = "Modify hg configuration", description = "Modifies the global mercurial configuration.", tags = "Mercurial", operationId = "hg_put_config")
|
@Operation(
|
||||||
|
summary = "Modify hg configuration",
|
||||||
|
description = "Modifies the global mercurial configuration.",
|
||||||
|
tags = "Mercurial",
|
||||||
|
operationId = "hg_put_config",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
content = @Content(
|
||||||
|
mediaType = HgVndMediaType.CONFIG,
|
||||||
|
schema = @Schema(implementation = UpdateHgConfigDto.class),
|
||||||
|
examples = @ExampleObject(
|
||||||
|
name = "Overwrites current configuration with this one.",
|
||||||
|
value = "{\n \"disabled\":false,\n \"hgBinary\":\"hg\",\n \"pythonBinary\":\"python\",\n \"pythonPath\":\"\",\n \"encoding\":\"UTF-8\",\n \"useOptimizedBytecode\":false,\n \"showRevisionInId\":false,\n \"disableHookSSLValidation\":false,\n \"enableHttpPostArgs\":false\n}",
|
||||||
|
summary = "Simple update configuration"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "204",
|
responseCode = "204",
|
||||||
description = "update success"
|
description = "update success"
|
||||||
|
|||||||
@@ -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.api.v2.resources;
|
||||||
|
|
||||||
|
interface UpdateHgConfigDto {
|
||||||
|
boolean isDisabled();
|
||||||
|
|
||||||
|
String getHgBinary();
|
||||||
|
|
||||||
|
String getPythonBinary();
|
||||||
|
|
||||||
|
String getPythonPath();
|
||||||
|
|
||||||
|
String getEncoding();
|
||||||
|
|
||||||
|
boolean isUseOptimizedBytecode();
|
||||||
|
|
||||||
|
boolean isShowRevisionInId();
|
||||||
|
|
||||||
|
boolean isEnableHttpPostArgs();
|
||||||
|
}
|
||||||
@@ -1,391 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.SCMContext;
|
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
import sonia.scm.util.Util;
|
|
||||||
import sonia.scm.web.HgUtil;
|
|
||||||
|
|
||||||
import javax.xml.bind.JAXBException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class AbstractHgHandler
|
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_ID_REVISION = "SCM_ID_REVISION";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_NODE = "HG_NODE";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_PAGE_LIMIT = "SCM_PAGE_LIMIT";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_PAGE_START = "SCM_PAGE_START";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_PATH = "SCM_PATH";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_REVISION = "SCM_REVISION";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_REVISION_END = "SCM_REVISION_END";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected static final String ENV_REVISION_START = "SCM_REVISION_START";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String ENCODING = "UTF-8";
|
|
||||||
|
|
||||||
/** mercurial encoding */
|
|
||||||
private static final String ENV_HGENCODING = "HGENCODING";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String ENV_PENDING = "HG_PENDING";
|
|
||||||
|
|
||||||
/** python encoding */
|
|
||||||
private static final String ENV_PYTHONIOENCODING = "PYTHONIOENCODING";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String ENV_PYTHONPATH = "PYTHONPATH";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for AbstractHgCommand
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(AbstractHgHandler.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* @param context
|
|
||||||
* @param repository
|
|
||||||
*/
|
|
||||||
protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context,
|
|
||||||
Repository repository)
|
|
||||||
{
|
|
||||||
this(handler, context, repository, handler.getDirectory(repository.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* @param context
|
|
||||||
* @param repository
|
|
||||||
* @param repositoryDirectory
|
|
||||||
*/
|
|
||||||
protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context,
|
|
||||||
Repository repository, File repositoryDirectory)
|
|
||||||
{
|
|
||||||
this.handler = handler;
|
|
||||||
this.context = context;
|
|
||||||
this.repository = repository;
|
|
||||||
this.repositoryDirectory = repositoryDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param revision
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Map<String, String> createEnvironment(String revision, String path)
|
|
||||||
{
|
|
||||||
Map<String, String> env = new HashMap<>();
|
|
||||||
|
|
||||||
env.put(ENV_REVISION, HgUtil.getRevision(revision));
|
|
||||||
env.put(ENV_PATH, Util.nonNull(path));
|
|
||||||
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
protected Process createHgProcess(String... args) throws IOException
|
|
||||||
{
|
|
||||||
return createHgProcess(new HashMap<String, String>(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param extraEnv
|
|
||||||
* @param args
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
protected Process createHgProcess(Map<String, String> extraEnv,
|
|
||||||
String... args)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
return createProcess(extraEnv, handler.getConfig().getHgBinary(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param script
|
|
||||||
* @param extraEnv
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
protected Process createScriptProcess(HgPythonScript script,
|
|
||||||
Map<String, String> extraEnv)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
return createProcess(extraEnv, handler.getConfig().getPythonBinary(),
|
|
||||||
script.getFile(SCMContext.getContext()).getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param errorStream
|
|
||||||
*/
|
|
||||||
protected void handleErrorStream(final InputStream errorStream)
|
|
||||||
{
|
|
||||||
if (errorStream != null)
|
|
||||||
{
|
|
||||||
new Thread(new Runnable()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String content = IOUtil.getContent(errorStream);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(content))
|
|
||||||
{
|
|
||||||
logger.error(content.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
logger.error("error during logging", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
protected <T> T getResultFromScript(Class<T> resultType, HgPythonScript script) throws IOException {
|
|
||||||
return getResultFromScript(resultType, script,
|
|
||||||
new HashMap<String, String>());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <T> T getResultFromScript(Class<T> resultType,
|
|
||||||
HgPythonScript script, Map<String, String> extraEnv)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
Process p = createScriptProcess(script, extraEnv);
|
|
||||||
|
|
||||||
handleErrorStream(p.getErrorStream());
|
|
||||||
try (InputStream input = p.getInputStream()) {
|
|
||||||
return (T) handler.getJaxbContext().createUnmarshaller().unmarshal(input);
|
|
||||||
} catch (JAXBException ex) {
|
|
||||||
logger.error("could not parse result", ex);
|
|
||||||
|
|
||||||
throw new InternalRepositoryException(repository, "could not parse result", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param extraEnv
|
|
||||||
* @param cmd
|
|
||||||
* @param args
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private Process createProcess(Map<String, String> extraEnv, String cmd,
|
|
||||||
String... args)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
HgConfig config = handler.getConfig();
|
|
||||||
List<String> cmdList = new ArrayList<String>();
|
|
||||||
|
|
||||||
cmdList.add(cmd);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(args))
|
|
||||||
{
|
|
||||||
cmdList.addAll(Arrays.asList(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
StringBuilder msg = new StringBuilder("create process for [");
|
|
||||||
Iterator<String> it = cmdList.iterator();
|
|
||||||
|
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
msg.append(it.next());
|
|
||||||
|
|
||||||
if (it.hasNext())
|
|
||||||
{
|
|
||||||
msg.append(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.append("]");
|
|
||||||
logger.debug(msg.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(cmdList);
|
|
||||||
|
|
||||||
pb.directory(repositoryDirectory);
|
|
||||||
|
|
||||||
Map<String, String> env = pb.environment();
|
|
||||||
|
|
||||||
// force utf-8 encoding for mercurial and python
|
|
||||||
env.put(ENV_PYTHONIOENCODING, ENCODING);
|
|
||||||
env.put(ENV_HGENCODING, ENCODING);
|
|
||||||
|
|
||||||
//J-
|
|
||||||
env.put(ENV_ID_REVISION,
|
|
||||||
String.valueOf(handler.getConfig().isShowRevisionInId())
|
|
||||||
);
|
|
||||||
//J+
|
|
||||||
|
|
||||||
if (context.isSystemEnvironment())
|
|
||||||
{
|
|
||||||
env.putAll(System.getenv());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.isPending())
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("enable hg pending for {}",
|
|
||||||
repositoryDirectory.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
env.put(ENV_PENDING, repositoryDirectory.getAbsolutePath());
|
|
||||||
|
|
||||||
if (extraEnv.containsKey(ENV_REVISION_START))
|
|
||||||
{
|
|
||||||
env.put(ENV_NODE, extraEnv.get(ENV_REVISION_START));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env.put(ENV_PYTHONPATH, HgUtil.getPythonPath(config));
|
|
||||||
env.put(ENV_REPOSITORY_PATH, repositoryDirectory.getAbsolutePath());
|
|
||||||
env.putAll(extraEnv);
|
|
||||||
|
|
||||||
if (logger.isTraceEnabled())
|
|
||||||
{
|
|
||||||
StringBuilder msg = new StringBuilder("start process in directory '");
|
|
||||||
|
|
||||||
msg.append(repositoryDirectory.getAbsolutePath()).append(
|
|
||||||
"' with env: \n");
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> e : env.entrySet())
|
|
||||||
{
|
|
||||||
msg.append(" ").append(e.getKey());
|
|
||||||
msg.append(" = ").append(e.getValue());
|
|
||||||
msg.append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace(msg.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return pb.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected Repository repository;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected File repositoryDirectory;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgContext context;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgRepositoryHandler handler;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import sonia.scm.TransactionId;
|
||||||
|
import sonia.scm.repository.hooks.HookEnvironment;
|
||||||
|
import sonia.scm.repository.hooks.HookServer;
|
||||||
|
import sonia.scm.security.AccessToken;
|
||||||
|
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||||
|
import sonia.scm.security.CipherUtil;
|
||||||
|
import sonia.scm.security.Xsrf;
|
||||||
|
import sonia.scm.web.HgUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_HOOK_PORT = "SCM_HOOK_PORT";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_CHALLENGE = "SCM_CHALLENGE";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENV_TRANSACTION_ID = "SCM_TRANSACTION_ID";
|
||||||
|
|
||||||
|
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
|
||||||
|
private final HgRepositoryHandler repositoryHandler;
|
||||||
|
private final HookEnvironment hookEnvironment;
|
||||||
|
private final HookServer server;
|
||||||
|
|
||||||
|
private int hookPort = -1;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DefaultHgEnvironmentBuilder(
|
||||||
|
AccessTokenBuilderFactory accessTokenBuilderFactory, HgRepositoryHandler repositoryHandler,
|
||||||
|
HookEnvironment hookEnvironment, HookServer server
|
||||||
|
) {
|
||||||
|
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
|
||||||
|
this.repositoryHandler = repositoryHandler;
|
||||||
|
this.hookEnvironment = hookEnvironment;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> read(Repository repository) {
|
||||||
|
ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
|
||||||
|
read(env, repository);
|
||||||
|
return env.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> write(Repository repository) {
|
||||||
|
ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
|
||||||
|
read(env, repository);
|
||||||
|
write(env);
|
||||||
|
return env.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void read(ImmutableMap.Builder<String, String> env, Repository repository) {
|
||||||
|
HgConfig config = repositoryHandler.getConfig();
|
||||||
|
env.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(config));
|
||||||
|
|
||||||
|
File directory = repositoryHandler.getDirectory(repository.getId());
|
||||||
|
|
||||||
|
env.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
|
||||||
|
env.put(ENV_REPOSITORY_ID, repository.getId());
|
||||||
|
env.put(ENV_REPOSITORY_PATH, directory.getAbsolutePath());
|
||||||
|
|
||||||
|
// enable experimental httppostargs protocol of mercurial
|
||||||
|
// Issue 970: https://goo.gl/poascp
|
||||||
|
env.put(ENV_HTTP_POST_ARGS, String.valueOf(config.isEnableHttpPostArgs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(ImmutableMap.Builder<String, String> env) {
|
||||||
|
env.put(ENV_HOOK_PORT, String.valueOf(getHookPort()));
|
||||||
|
env.put(ENV_BEARER_TOKEN, accessToken());
|
||||||
|
env.put(ENV_CHALLENGE, hookEnvironment.getChallenge());
|
||||||
|
TransactionId.get().ifPresent(transactionId -> env.put(ENV_TRANSACTION_ID, transactionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String accessToken() {
|
||||||
|
AccessToken accessToken = accessTokenBuilderFactory.create()
|
||||||
|
// disable xsrf protection, because we can not access the http servlet request for verification
|
||||||
|
.custom(Xsrf.TOKEN_KEY, null)
|
||||||
|
.build();
|
||||||
|
return CipherUtil.getInstance().encode(accessToken.compact());
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized int getHookPort() {
|
||||||
|
if (hookPort > 0) {
|
||||||
|
return hookPort;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
hookPort = server.start();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("failed to start mercurial hook server");
|
||||||
|
}
|
||||||
|
return hookPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,20 +36,10 @@ import javax.xml.bind.annotation.XmlTransient;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "config")
|
@XmlRootElement(name = "config")
|
||||||
public class HgConfig extends RepositoryConfig
|
public class HgConfig extends RepositoryConfig {
|
||||||
{
|
|
||||||
|
|
||||||
public static final String PERMISSION = "hg";
|
public static final String PERMISSION = "hg";
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public HgConfig() {}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
@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() {
|
||||||
@@ -123,10 +113,6 @@ public class HgConfig extends RepositoryConfig
|
|||||||
return useOptimizedBytecode;
|
return useOptimizedBytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDisableHookSSLValidation() {
|
|
||||||
return disableHookSSLValidation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnableHttpPostArgs() {
|
public boolean isEnableHttpPostArgs() {
|
||||||
return enableHttpPostArgs;
|
return enableHttpPostArgs;
|
||||||
}
|
}
|
||||||
@@ -216,10 +202,6 @@ public class HgConfig extends RepositoryConfig
|
|||||||
this.useOptimizedBytecode = useOptimizedBytecode;
|
this.useOptimizedBytecode = useOptimizedBytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
|
|
||||||
this.disableHookSSLValidation = disableHookSSLValidation;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
@@ -242,9 +224,4 @@ public class HgConfig extends RepositoryConfig
|
|||||||
|
|
||||||
private boolean enableHttpPostArgs = false;
|
private boolean enableHttpPostArgs = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* disable validation of ssl certificates for mercurial hook
|
|
||||||
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>
|
|
||||||
*/
|
|
||||||
private boolean disableHookSSLValidation = false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class HgContext
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public HgContext() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param pending
|
|
||||||
*/
|
|
||||||
public HgContext(boolean pending)
|
|
||||||
{
|
|
||||||
this.pending = pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param pending
|
|
||||||
* @param systemEnvironment
|
|
||||||
*/
|
|
||||||
public HgContext(boolean pending, boolean systemEnvironment)
|
|
||||||
{
|
|
||||||
this.pending = pending;
|
|
||||||
this.systemEnvironment = systemEnvironment;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isPending()
|
|
||||||
{
|
|
||||||
return pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isSystemEnvironment()
|
|
||||||
{
|
|
||||||
return systemEnvironment;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param pending
|
|
||||||
*/
|
|
||||||
public void setPending(boolean pending)
|
|
||||||
{
|
|
||||||
this.pending = pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param systemEnvironment
|
|
||||||
*/
|
|
||||||
public void setSystemEnvironment(boolean systemEnvironment)
|
|
||||||
{
|
|
||||||
this.systemEnvironment = systemEnvironment;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private boolean pending = false;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private boolean systemEnvironment = true;
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.inject.OutOfScopeException;
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.ProvisionException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection provider for {@link HgContext}.
|
|
||||||
* This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in
|
|
||||||
* request scope (mostly because the scope is not available) a new {@link HgContext} gets returned.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class HgContextProvider implements Provider<HgContext>
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the LOG for HgContextProvider
|
|
||||||
*/
|
|
||||||
private static final Logger LOG =
|
|
||||||
LoggerFactory.getLogger(HgContextProvider.class);
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
private Provider<HgContextRequestStore> requestStoreProvider;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public HgContextProvider(Provider<HgContextRequestStore> requestStoreProvider) {
|
|
||||||
this.requestStoreProvider = requestStoreProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public HgContextProvider() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HgContext get() {
|
|
||||||
HgContext context = fetchContextFromRequest();
|
|
||||||
if (context != null) {
|
|
||||||
LOG.trace("return HgContext from request store");
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
LOG.trace("could not find context in request scope, returning new instance");
|
|
||||||
return new HgContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private HgContext fetchContextFromRequest() {
|
|
||||||
try {
|
|
||||||
if (requestStoreProvider != null) {
|
|
||||||
return requestStoreProvider.get().get();
|
|
||||||
} else {
|
|
||||||
LOG.trace("no request store provider defined, could not return context from request");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (ProvisionException ex) {
|
|
||||||
if (ex.getCause() instanceof OutOfScopeException) {
|
|
||||||
LOG.trace("we are currently out of request scope, failed to retrieve context");
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.ProvisionException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.security.AccessToken;
|
|
||||||
import sonia.scm.security.CipherUtil;
|
|
||||||
import sonia.scm.security.Xsrf;
|
|
||||||
import sonia.scm.web.HgUtil;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public final class HgEnvironment
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(HgEnvironment.class);
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String ENV_CHALLENGE = "SCM_CHALLENGE";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String ENV_URL = "SCM_URL";
|
|
||||||
|
|
||||||
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
|
||||||
|
|
||||||
private static final String SCM_XSRF = "SCM_XSRF";
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private HgEnvironment() {}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param environment
|
|
||||||
* @param handler
|
|
||||||
* @param hookManager
|
|
||||||
*/
|
|
||||||
public static void prepareEnvironment(Map<String, String> environment,
|
|
||||||
HgRepositoryHandler handler, HgHookManager hookManager)
|
|
||||||
{
|
|
||||||
prepareEnvironment(environment, handler, hookManager, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param environment
|
|
||||||
* @param handler
|
|
||||||
* @param hookManager
|
|
||||||
* @param request
|
|
||||||
*/
|
|
||||||
public static void prepareEnvironment(Map<String, String> environment,
|
|
||||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
|
||||||
HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String hookUrl;
|
|
||||||
|
|
||||||
if (request != null)
|
|
||||||
{
|
|
||||||
hookUrl = hookManager.createUrl(request);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hookUrl = hookManager.createUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
AccessToken accessToken = hookManager.getAccessToken();
|
|
||||||
environment.put(SCM_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact()));
|
|
||||||
extractXsrfKey(environment, accessToken);
|
|
||||||
} catch (ProvisionException e) {
|
|
||||||
LOG.debug("could not create bearer token; looks like currently we are not in a request; probably you can ignore the following exception:", e);
|
|
||||||
}
|
|
||||||
environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig()));
|
|
||||||
environment.put(ENV_URL, hookUrl);
|
|
||||||
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void extractXsrfKey(Map<String, String> environment, AccessToken accessToken) {
|
|
||||||
environment.put(SCM_XSRF, accessToken.<String>getCustom(Xsrf.TOKEN_KEY).orElse("-"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,25 +24,12 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import com.google.inject.servlet.RequestScoped;
|
import com.google.inject.ImplementedBy;
|
||||||
|
|
||||||
/**
|
import java.util.Map;
|
||||||
* Holds an instance of {@link HgContext} in the request scope.
|
|
||||||
*
|
|
||||||
* <p>The problem seems to be that guice had multiple options for injecting HgContext. {@link HgContextProvider}
|
|
||||||
* bound via Module and {@link HgContext} bound void {@link RequestScoped} annotation. It looks like that Guice 4
|
|
||||||
* injects randomly the one or the other, in SCMv1 (Guice 3) everything works as expected.</p>
|
|
||||||
*
|
|
||||||
* <p>To fix the problem we have created this class annotated with {@link RequestScoped}, which holds an instance
|
|
||||||
* of {@link HgContext}. This way only the {@link HgContextProvider} is used for injection.</p>
|
|
||||||
*/
|
|
||||||
@RequestScoped
|
|
||||||
public class HgContextRequestStore {
|
|
||||||
|
|
||||||
private final HgContext context = new HgContext();
|
|
||||||
|
|
||||||
public HgContext get() {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ImplementedBy(DefaultHgEnvironmentBuilder.class)
|
||||||
|
public interface HgEnvironmentBuilder {
|
||||||
|
Map<String, String> read(Repository repository);
|
||||||
|
Map<String, String> write(Repository repository);
|
||||||
}
|
}
|
||||||
@@ -1,354 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.github.legman.Subscribe;
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.OutOfScopeException;
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.ProvisionException;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.config.ScmConfiguration;
|
|
||||||
import sonia.scm.config.ScmConfigurationChangedEvent;
|
|
||||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
|
||||||
import sonia.scm.security.AccessToken;
|
|
||||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
import sonia.scm.util.Util;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class HgHookManager {
|
|
||||||
|
|
||||||
@SuppressWarnings("java:S1075") // this url is fixed
|
|
||||||
private static final String URL_HOOKPATH = "/hook/hg/";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for HgHookManager
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(HgHookManager.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
* @param configuration
|
|
||||||
* @param httpServletRequestProvider
|
|
||||||
* @param httpClient
|
|
||||||
* @param accessTokenBuilderFactory
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public HgHookManager(ScmConfiguration configuration,
|
|
||||||
Provider<HttpServletRequest> httpServletRequestProvider,
|
|
||||||
AdvancedHttpClient httpClient, AccessTokenBuilderFactory accessTokenBuilderFactory)
|
|
||||||
{
|
|
||||||
this.configuration = configuration;
|
|
||||||
this.httpServletRequestProvider = httpServletRequestProvider;
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
*/
|
|
||||||
@Subscribe(async = false)
|
|
||||||
public void configChanged(ScmConfigurationChangedEvent config)
|
|
||||||
{
|
|
||||||
hookUrl = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String createUrl(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
if (hookUrl == null)
|
|
||||||
{
|
|
||||||
synchronized (this)
|
|
||||||
{
|
|
||||||
if (hookUrl == null)
|
|
||||||
{
|
|
||||||
buildHookUrl(request);
|
|
||||||
|
|
||||||
if (logger.isInfoEnabled() && Util.isNotEmpty(hookUrl))
|
|
||||||
{
|
|
||||||
logger.info("use {} for mercurial hooks", hookUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hookUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String createUrl()
|
|
||||||
{
|
|
||||||
String url = hookUrl;
|
|
||||||
|
|
||||||
if (url == null)
|
|
||||||
{
|
|
||||||
HttpServletRequest request = getHttpServletRequest();
|
|
||||||
|
|
||||||
if (request != null)
|
|
||||||
{
|
|
||||||
url = createUrl(request);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
url = createConfiguredUrl();
|
|
||||||
logger.warn(
|
|
||||||
"created url {} without request, in some cases this could cause problems",
|
|
||||||
url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getChallenge()
|
|
||||||
{
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param challenge
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isAcceptAble(String challenge)
|
|
||||||
{
|
|
||||||
return this.challenge.equals(challenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessToken getAccessToken()
|
|
||||||
{
|
|
||||||
return accessTokenBuilderFactory.create().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildHookUrl(HttpServletRequest request) {
|
|
||||||
if (configuration.isForceBaseUrl()) {
|
|
||||||
logger.debug("create hook url from configured base url because force base url is enabled");
|
|
||||||
|
|
||||||
hookUrl = createConfiguredUrl();
|
|
||||||
if (!isUrlWorking(hookUrl)) {
|
|
||||||
disableHooks();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.debug("create hook url from request");
|
|
||||||
|
|
||||||
hookUrl = HttpUtil.getCompleteUrl(request, URL_HOOKPATH);
|
|
||||||
if (!isUrlWorking(hookUrl)) {
|
|
||||||
logger.warn("hook url {} from request does not work, try now localhost", hookUrl);
|
|
||||||
|
|
||||||
hookUrl = createLocalUrl(request);
|
|
||||||
if (!isUrlWorking(hookUrl)) {
|
|
||||||
logger.warn("localhost hook url {} does not work, try now from configured base url", hookUrl);
|
|
||||||
|
|
||||||
hookUrl = createConfiguredUrl();
|
|
||||||
if (!isUrlWorking(hookUrl)) {
|
|
||||||
disableHooks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private String createConfiguredUrl()
|
|
||||||
{
|
|
||||||
//J-
|
|
||||||
return HttpUtil.getUriWithoutEndSeperator(
|
|
||||||
MoreObjects.firstNonNull(
|
|
||||||
configuration.getBaseUrl(),
|
|
||||||
"http://localhost:8080/scm"
|
|
||||||
)
|
|
||||||
).concat(URL_HOOKPATH);
|
|
||||||
//J+
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private String createLocalUrl(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder(request.getScheme());
|
|
||||||
|
|
||||||
sb.append("://localhost:").append(request.getLocalPort());
|
|
||||||
sb.append(request.getContextPath()).append(URL_HOOKPATH);
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private void disableHooks()
|
|
||||||
{
|
|
||||||
if (logger.isErrorEnabled())
|
|
||||||
{
|
|
||||||
logger.error(
|
|
||||||
"disabling mercurial hooks, because hook url {} seems not to work",
|
|
||||||
hookUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
hookUrl = Util.EMPTY_STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private HttpServletRequest getHttpServletRequest()
|
|
||||||
{
|
|
||||||
HttpServletRequest request = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
request = httpServletRequestProvider.get();
|
|
||||||
}
|
|
||||||
catch (ProvisionException | OutOfScopeException ex)
|
|
||||||
{
|
|
||||||
logger.debug("http servlet request is not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isUrlWorking(String url)
|
|
||||||
{
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
url = url.concat("?ping=true");
|
|
||||||
|
|
||||||
logger.trace("check hook url {}", url);
|
|
||||||
//J-
|
|
||||||
int sc = httpClient.get(url)
|
|
||||||
.disableHostnameValidation(true)
|
|
||||||
.disableCertificateValidation(true)
|
|
||||||
.ignoreProxySettings(true)
|
|
||||||
.disableTracing()
|
|
||||||
.request()
|
|
||||||
.getStatus();
|
|
||||||
//J+
|
|
||||||
result = sc == 204;
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
if (logger.isTraceEnabled())
|
|
||||||
{
|
|
||||||
logger.trace("url test failed for url ".concat(url), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String challenge = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private ScmConfiguration configuration;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private volatile String hookUrl;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private AdvancedHttpClient httpClient;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Provider<HttpServletRequest> httpServletRequestProvider;
|
|
||||||
|
|
||||||
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
|
|
||||||
}
|
|
||||||
@@ -38,80 +38,30 @@ import java.io.File;
|
|||||||
*/
|
*/
|
||||||
public enum HgPythonScript {
|
public enum HgPythonScript {
|
||||||
|
|
||||||
HOOK("scmhooks.py"), HGWEB("hgweb.py"), VERSION("version.py");
|
HOOK("scmhooks.py"), HGWEB("hgweb.py");
|
||||||
|
|
||||||
/** Field description */
|
private static final String BASE_DIRECTORY = "lib".concat(File.separator).concat("python");
|
||||||
private static final String BASE_DIRECTORY =
|
|
||||||
"lib".concat(File.separator).concat("python");
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String BASE_RESOURCE = "/sonia/scm/python/";
|
private static final String BASE_RESOURCE = "/sonia/scm/python/";
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private final String name;
|
||||||
|
|
||||||
/**
|
HgPythonScript(String name) {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
private HgPythonScript(String name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
public static File getScriptDirectory(SCMContextProvider context) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static File getScriptDirectory(SCMContextProvider context)
|
|
||||||
{
|
|
||||||
return new File(context.getBaseDirectory(), BASE_DIRECTORY);
|
return new File(context.getBaseDirectory(), BASE_DIRECTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public File getFile(SCMContextProvider context) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public File getFile(SCMContextProvider context)
|
|
||||||
{
|
|
||||||
return new File(getScriptDirectory(context), name);
|
return new File(getScriptDirectory(context), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getName() {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getResourcePath() {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getResourcePath()
|
|
||||||
{
|
|
||||||
return BASE_RESOURCE.concat(name);
|
return BASE_RESOURCE.concat(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.aragost.javahg.RepositoryConfiguration;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.hooks.HookEnvironment;
|
||||||
|
import sonia.scm.repository.spi.javahg.HgFileviewExtension;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class HgRepositoryFactory {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryFactory.class);
|
||||||
|
|
||||||
|
private final HgRepositoryHandler handler;
|
||||||
|
private final HookEnvironment hookEnvironment;
|
||||||
|
private final HgEnvironmentBuilder environmentBuilder;
|
||||||
|
private final Function<Repository, File> directoryResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder) {
|
||||||
|
this(
|
||||||
|
handler, hookEnvironment, environmentBuilder,
|
||||||
|
repository -> handler.getDirectory(repository.getId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder, Function<Repository, File> directoryResolver) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.hookEnvironment = hookEnvironment;
|
||||||
|
this.environmentBuilder = environmentBuilder;
|
||||||
|
this.directoryResolver = directoryResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.aragost.javahg.Repository openForRead(Repository repository) {
|
||||||
|
return open(repository, environmentBuilder.read(repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.aragost.javahg.Repository openForWrite(Repository repository) {
|
||||||
|
return open(repository, environmentBuilder.write(repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
private com.aragost.javahg.Repository open(Repository repository, Map<String, String> environment) {
|
||||||
|
File directory = directoryResolver.apply(repository);
|
||||||
|
|
||||||
|
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
||||||
|
repoConfiguration.getEnvironment().putAll(environment);
|
||||||
|
repoConfiguration.addExtension(HgFileviewExtension.class);
|
||||||
|
|
||||||
|
boolean pending = hookEnvironment.isPending();
|
||||||
|
repoConfiguration.setEnablePendingChangesets(pending);
|
||||||
|
|
||||||
|
Charset encoding = encoding();
|
||||||
|
repoConfiguration.setEncoding(encoding);
|
||||||
|
|
||||||
|
repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
|
||||||
|
|
||||||
|
LOG.trace("open hg repository {}: encoding: {}, pending: {}", directory, encoding, pending);
|
||||||
|
|
||||||
|
return com.aragost.javahg.Repository.open(repoConfiguration, directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Charset encoding() {
|
||||||
|
String charset = handler.getConfig().getEncoding();
|
||||||
|
try {
|
||||||
|
return Charset.forName(charset);
|
||||||
|
} catch (UnsupportedCharsetException ex) {
|
||||||
|
LOG.warn("unknown charset {} in hg config, fallback to utf-8", charset);
|
||||||
|
return StandardCharsets.UTF_8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,11 +27,9 @@ package sonia.scm.repository;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.ConfigurationException;
|
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.autoconfig.AutoConfigurator;
|
import sonia.scm.autoconfig.AutoConfigurator;
|
||||||
import sonia.scm.installer.HgInstaller;
|
import sonia.scm.installer.HgInstaller;
|
||||||
@@ -43,14 +41,14 @@ import sonia.scm.io.INISection;
|
|||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
||||||
|
import sonia.scm.repository.spi.HgVersionCommand;
|
||||||
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
import sonia.scm.util.SystemUtil;
|
import sonia.scm.util.SystemUtil;
|
||||||
|
|
||||||
import javax.xml.bind.JAXBContext;
|
|
||||||
import javax.xml.bind.JAXBException;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -63,14 +61,15 @@ import java.util.Optional;
|
|||||||
public class HgRepositoryHandler
|
public class HgRepositoryHandler
|
||||||
extends AbstractSimpleRepositoryHandler<HgConfig> {
|
extends AbstractSimpleRepositoryHandler<HgConfig> {
|
||||||
|
|
||||||
public static final String PATH_HOOK = ".hook-1.8";
|
|
||||||
public static final String RESOURCE_VERSION = "sonia/scm/version/scm-hg-plugin";
|
public static final String RESOURCE_VERSION = "sonia/scm/version/scm-hg-plugin";
|
||||||
public static final String TYPE_DISPLAYNAME = "Mercurial";
|
public static final String TYPE_DISPLAYNAME = "Mercurial";
|
||||||
public static final String TYPE_NAME = "hg";
|
public static final String TYPE_NAME = "hg";
|
||||||
public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME,
|
public static final RepositoryType TYPE = new RepositoryType(
|
||||||
|
TYPE_NAME,
|
||||||
TYPE_DISPLAYNAME,
|
TYPE_DISPLAYNAME,
|
||||||
HgRepositoryServiceProvider.COMMANDS,
|
HgRepositoryServiceProvider.COMMANDS,
|
||||||
HgRepositoryServiceProvider.FEATURES);
|
HgRepositoryServiceProvider.FEATURES
|
||||||
|
);
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HgRepositoryHandler.class);
|
private static final Logger logger = LoggerFactory.getLogger(HgRepositoryHandler.class);
|
||||||
|
|
||||||
@@ -78,28 +77,14 @@ public class HgRepositoryHandler
|
|||||||
private static final String CONFIG_SECTION_SCMM = "scmm";
|
private static final String CONFIG_SECTION_SCMM = "scmm";
|
||||||
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
|
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
|
||||||
|
|
||||||
private final Provider<HgContext> hgContextProvider;
|
|
||||||
|
|
||||||
private final HgWorkingCopyFactory workingCopyFactory;
|
private final HgWorkingCopyFactory workingCopyFactory;
|
||||||
|
|
||||||
private final JAXBContext jaxbContext;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||||
Provider<HgContext> hgContextProvider,
|
|
||||||
RepositoryLocationResolver repositoryLocationResolver,
|
RepositoryLocationResolver repositoryLocationResolver,
|
||||||
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
|
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
|
||||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||||
this.hgContextProvider = hgContextProvider;
|
|
||||||
this.workingCopyFactory = workingCopyFactory;
|
this.workingCopyFactory = workingCopyFactory;
|
||||||
|
|
||||||
try {
|
|
||||||
this.jaxbContext = JAXBContext.newInstance(BrowserResult.class,
|
|
||||||
BlameResult.class, Changeset.class, ChangesetPagingResult.class,
|
|
||||||
HgVersion.class);
|
|
||||||
} catch (JAXBException ex) {
|
|
||||||
throw new ConfigurationException("could not create jaxbcontext", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doAutoConfiguration(HgConfig autoConfig) {
|
public void doAutoConfiguration(HgConfig autoConfig) {
|
||||||
@@ -107,8 +92,7 @@ public class HgRepositoryHandler
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("installing mercurial with {}",
|
logger.debug("installing mercurial with {}", installer.getClass().getName());
|
||||||
installer.getClass().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
installer.install(baseDirectory, autoConfig);
|
installer.install(baseDirectory, autoConfig);
|
||||||
@@ -154,16 +138,6 @@ public class HgRepositoryHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HgContext getHgContext() {
|
|
||||||
HgContext context = hgContextProvider.get();
|
|
||||||
|
|
||||||
if (context == null) {
|
|
||||||
context = new HgContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImportHandler getImportHandler() {
|
public ImportHandler getImportHandler() {
|
||||||
return new HgImportHandler(this);
|
return new HgImportHandler(this);
|
||||||
@@ -176,28 +150,14 @@ public class HgRepositoryHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVersionInformation() {
|
public String getVersionInformation() {
|
||||||
String version = getStringFromResource(RESOURCE_VERSION,
|
return getVersionInformation(new HgVersionCommand(getConfig()));
|
||||||
DEFAULT_VERSION_INFORMATION);
|
}
|
||||||
|
|
||||||
try {
|
String getVersionInformation(HgVersionCommand command) {
|
||||||
HgVersion hgVersion = new HgVersionHandler(this, hgContextProvider.get(),
|
String version = getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION);
|
||||||
baseDirectory).getVersion();
|
HgVersion hgVersion = command.get();
|
||||||
|
|
||||||
if (hgVersion != null) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("mercurial/python informations: {}", hgVersion);
|
logger.debug("mercurial/python informations: {}", hgVersion);
|
||||||
}
|
return MessageFormat.format(version, hgVersion.getPython(), hgVersion.getMercurial());
|
||||||
|
|
||||||
version = MessageFormat.format(version, hgVersion.getPython(),
|
|
||||||
hgVersion.getMercurial());
|
|
||||||
} else if (logger.isWarnEnabled()) {
|
|
||||||
logger.warn("could not retrieve version informations");
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.error("could not read version informations", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -253,28 +213,24 @@ public class HgRepositoryHandler
|
|||||||
logger.debug("write python script {}", script.getName());
|
logger.debug("write python script {}", script.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream content = null;
|
try (InputStream content = input(script); OutputStream output = output(context, script)) {
|
||||||
OutputStream output = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
content = HgRepositoryHandler.class.getResourceAsStream(
|
|
||||||
script.getResourcePath());
|
|
||||||
output = new FileOutputStream(script.getFile(context));
|
|
||||||
IOUtil.copy(content, output);
|
IOUtil.copy(content, output);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
logger.error("could not write script", ex);
|
logger.error("could not write script", ex);
|
||||||
} finally {
|
|
||||||
IOUtil.close(content);
|
|
||||||
IOUtil.close(output);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream input(HgPythonScript script) {
|
||||||
|
return HgRepositoryHandler.class.getResourceAsStream(script.getResourcePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream output(SCMContextProvider context, HgPythonScript script) throws FileNotFoundException {
|
||||||
|
return new FileOutputStream(script.getFile(context));
|
||||||
|
}
|
||||||
|
|
||||||
public HgWorkingCopyFactory getWorkingCopyFactory() {
|
public HgWorkingCopyFactory getWorkingCopyFactory() {
|
||||||
return workingCopyFactory;
|
return workingCopyFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JAXBContext getJaxbContext() {
|
|
||||||
return jaxbContext;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,8 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Data;
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
@@ -37,13 +35,14 @@ import javax.xml.bind.annotation.XmlRootElement;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
@XmlRootElement(name = "version")
|
@XmlRootElement(name = "version")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@EqualsAndHashCode
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public class HgVersion {
|
public class HgVersion {
|
||||||
|
|
||||||
|
public static final String UNKNOWN = "x.y.z (unknown)";
|
||||||
|
|
||||||
private String mercurial;
|
private String mercurial;
|
||||||
private String python;
|
private String python;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,70 +26,26 @@ package sonia.scm.repository.api;
|
|||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public final class HgHookMessage implements Serializable
|
@Getter
|
||||||
{
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
public final class HgHookMessage implements Serializable {
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final long serialVersionUID = 1804492842452344326L;
|
private static final long serialVersionUID = 1804492842452344326L;
|
||||||
|
|
||||||
//~--- constant enums -------------------------------------------------------
|
private Severity severity;
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static enum Severity { NOTE, ERROR; }
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param severity
|
|
||||||
* @param message
|
|
||||||
*/
|
|
||||||
public HgHookMessage(Severity severity, String message)
|
|
||||||
{
|
|
||||||
this.severity = severity;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getMessage()
|
|
||||||
{
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Severity getSeverity()
|
|
||||||
{
|
|
||||||
return severity;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
/** Field description */
|
public enum Severity { NOTE, ERROR }
|
||||||
private Severity severity;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.ExceptionWithContext;
|
||||||
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.TransactionId;
|
||||||
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
|
import sonia.scm.repository.api.HgHookMessage;
|
||||||
|
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||||
|
import sonia.scm.repository.spi.HookEventFacade;
|
||||||
|
import sonia.scm.security.BearerToken;
|
||||||
|
import sonia.scm.security.CipherUtil;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
class DefaultHookHandler implements HookHandler {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DefaultHookHandler.class);
|
||||||
|
|
||||||
|
private final HookEventFacade hookEventFacade;
|
||||||
|
private final HookEnvironment environment;
|
||||||
|
private final HookContextProviderFactory hookContextProviderFactory;
|
||||||
|
private final Socket socket;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DefaultHookHandler(HookContextProviderFactory hookContextProviderFactory, HookEventFacade hookEventFacade, HookEnvironment environment, @Assisted Socket socket) {
|
||||||
|
this.hookContextProviderFactory = hookContextProviderFactory;
|
||||||
|
this.hookEventFacade = hookEventFacade;
|
||||||
|
this.environment = environment;
|
||||||
|
this.socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LOG.trace("start handling hook protocol");
|
||||||
|
try (InputStream input = socket.getInputStream(); OutputStream output = socket.getOutputStream()) {
|
||||||
|
handleHookRequest(input, output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("failed to read hook request", e);
|
||||||
|
} finally {
|
||||||
|
LOG.trace("close client socket");
|
||||||
|
TransactionId.clear();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHookRequest(InputStream input, OutputStream output) throws IOException {
|
||||||
|
Request request = Sockets.receive(input, Request.class);
|
||||||
|
TransactionId.set(request.getTransactionId());
|
||||||
|
Response response = handleHookRequest(request);
|
||||||
|
Sockets.send(output, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response handleHookRequest(Request request) {
|
||||||
|
LOG.trace("process {} hook for node {}", request.getType(), request.getNode());
|
||||||
|
|
||||||
|
if (!environment.isAcceptAble(request.getChallenge())) {
|
||||||
|
LOG.warn("received hook with invalid challenge: {}", request.getChallenge());
|
||||||
|
return error("invalid hook challenge");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
authenticate(request);
|
||||||
|
|
||||||
|
return fireHook(request);
|
||||||
|
} catch (AuthenticationException ex) {
|
||||||
|
LOG.warn("hook authentication failed", ex);
|
||||||
|
return error("hook authentication failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private Response fireHook(Request request) {
|
||||||
|
HgHookContextProvider context = hookContextProviderFactory.create(request.getRepositoryId(), request.getNode());
|
||||||
|
|
||||||
|
try {
|
||||||
|
environment.setPending(request.getType() == RepositoryHookType.PRE_RECEIVE);
|
||||||
|
|
||||||
|
hookEventFacade.handle(request.getRepositoryId()).fireHookEvent(request.getType(), context);
|
||||||
|
|
||||||
|
return new Response(context.getHgMessageProvider().getMessages(), false);
|
||||||
|
|
||||||
|
} catch (NotFoundException ex) {
|
||||||
|
LOG.warn("could not find repository with id {}", request.getRepositoryId(), ex);
|
||||||
|
return error("repository not found");
|
||||||
|
} catch (ExceptionWithContext ex) {
|
||||||
|
LOG.debug("scm exception on hook occurred", ex);
|
||||||
|
return error(context, ex.getMessage());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.warn("unknown error on hook occurred", ex);
|
||||||
|
return error(context, "unknown error");
|
||||||
|
} finally {
|
||||||
|
environment.clearPendingState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authenticate(Request request) {
|
||||||
|
LOG.trace("authenticate hook request");
|
||||||
|
String token = CipherUtil.getInstance().decode(request.getToken());
|
||||||
|
BearerToken bearer = BearerToken.valueOf(token);
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
subject.login(bearer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response error(HgHookContextProvider context, String message) {
|
||||||
|
List<HgHookMessage> messages = new ArrayList<>(context.getHgMessageProvider().getMessages());
|
||||||
|
messages.add(createErrorMessage(message));
|
||||||
|
return new Response(messages, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response error(String message) {
|
||||||
|
return new Response(
|
||||||
|
singletonList(createErrorMessage(message)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private HgHookMessage createErrorMessage(String message) {
|
||||||
|
return new HgHookMessage(HgHookMessage.Severity.ERROR, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close() {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("failed to close hook socket", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Request {
|
||||||
|
private String token;
|
||||||
|
private RepositoryHookType type;
|
||||||
|
private String transactionId;
|
||||||
|
private String repositoryId;
|
||||||
|
private String challenge;
|
||||||
|
private String node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Response {
|
||||||
|
private List<HgHookMessage> messages;
|
||||||
|
private boolean abort;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,59 +22,36 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.AbstractHgHandler;
|
|
||||||
import sonia.scm.repository.HgContext;
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import java.io.File;
|
public class HookContextProviderFactory {
|
||||||
|
|
||||||
import java.util.Map;
|
private final RepositoryManager repositoryManager;
|
||||||
|
private final HgRepositoryHandler repositoryHandler;
|
||||||
|
private final HgRepositoryFactory repositoryFactory;
|
||||||
|
|
||||||
/**
|
@Inject
|
||||||
*
|
public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) {
|
||||||
* @author Sebastian Sdorra
|
this.repositoryManager = repositoryManager;
|
||||||
*/
|
this.repositoryHandler = repositoryHandler;
|
||||||
public class AbstractHgCommand extends AbstractHgHandler
|
this.repositoryFactory = repositoryFactory;
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* @param context
|
|
||||||
* @param repository
|
|
||||||
* @param repositoryDirectory
|
|
||||||
*/
|
|
||||||
protected AbstractHgCommand(HgRepositoryHandler handler, HgContext context,
|
|
||||||
Repository repository, File repositoryDirectory)
|
|
||||||
{
|
|
||||||
super(handler, context, repository, repositoryDirectory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
HgHookContextProvider create(String repositoryId, String node) {
|
||||||
|
Repository repository = repositoryManager.get(repositoryId);
|
||||||
/**
|
if (repository == null) {
|
||||||
* Method description
|
throw new NotFoundException(Repository.class, repositoryId);
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param revision
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Map<String,
|
|
||||||
String> createEnvironment(FileBaseCommandRequest request)
|
|
||||||
{
|
|
||||||
return createEnvironment(request.getRevision(), request.getPath());
|
|
||||||
}
|
}
|
||||||
|
return new HgHookContextProvider(repositoryHandler, repositoryFactory, repository, node);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class HookEnvironment {
|
||||||
|
|
||||||
|
private final ThreadLocal<Boolean> threadEnvironment = new ThreadLocal<>();
|
||||||
|
private final String challenge = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
public String getChallenge() {
|
||||||
|
return challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAcceptAble(String challenge) {
|
||||||
|
return this.challenge.equals(challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPending(boolean pending) {
|
||||||
|
threadEnvironment.set(pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearPendingState() {
|
||||||
|
threadEnvironment.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPending() {
|
||||||
|
Boolean threadState = threadEnvironment.get();
|
||||||
|
if (threadState != null) {
|
||||||
|
return threadState;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
public interface HookHandler extends Runnable {
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface HookHandlerFactory {
|
||||||
|
|
||||||
|
HookHandler create(Socket socket);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class HookModule extends AbstractModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
install(new FactoryModuleBuilder()
|
||||||
|
.implement(HookHandler.class, DefaultHookHandler.class)
|
||||||
|
.build(HookHandlerFactory.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class HookServer implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HookServer.class);
|
||||||
|
|
||||||
|
private final HookHandlerFactory handlerFactory;
|
||||||
|
|
||||||
|
private ExecutorService acceptor;
|
||||||
|
private ExecutorService workerPool;
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private SecurityManager securityManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HookServer(HookHandlerFactory handlerFactory) {
|
||||||
|
this.handlerFactory = handlerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int start() throws IOException {
|
||||||
|
securityManager = SecurityUtils.getSecurityManager();
|
||||||
|
|
||||||
|
acceptor = createAcceptor();
|
||||||
|
workerPool = createWorkerPool();
|
||||||
|
serverSocket = createServerSocket();
|
||||||
|
// set timeout to 2 min, to avoid blocking clients
|
||||||
|
serverSocket.setSoTimeout(2 * 60 * 1000);
|
||||||
|
|
||||||
|
accept();
|
||||||
|
|
||||||
|
int port = serverSocket.getLocalPort();
|
||||||
|
LOG.info("open hg hook server on port {}", port);
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void accept() {
|
||||||
|
acceptor.submit(() -> {
|
||||||
|
while (!serverSocket.isClosed()) {
|
||||||
|
try {
|
||||||
|
LOG.trace("wait for next hook connection");
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
LOG.trace("accept incoming hook client from {}", clientSocket.getInetAddress());
|
||||||
|
HookHandler hookHandler = handlerFactory.create(clientSocket);
|
||||||
|
workerPool.submit(associateSecurityManager(hookHandler));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.debug("failed to accept socket, possible closed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.warn("ServerSocket is closed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable associateSecurityManager(HookHandler hookHandler) {
|
||||||
|
return () -> {
|
||||||
|
ThreadContext.bind(securityManager);
|
||||||
|
try {
|
||||||
|
hookHandler.run();
|
||||||
|
} finally {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
ThreadContext.unbindSecurityManager();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private ServerSocket createServerSocket() throws IOException {
|
||||||
|
return new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutorService createAcceptor() {
|
||||||
|
return Executors.newSingleThreadExecutor(
|
||||||
|
createThreadFactory("HgHookAcceptor")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutorService createWorkerPool() {
|
||||||
|
return Executors.newCachedThreadPool(
|
||||||
|
createThreadFactory("HgHookWorker-%d")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private ThreadFactory createThreadFactory(String hgHookAcceptor) {
|
||||||
|
return new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat(hgHookAcceptor)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
closeSocket();
|
||||||
|
shutdown(acceptor);
|
||||||
|
shutdown(workerPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSocket() {
|
||||||
|
if (serverSocket != null) {
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.warn("failed to close server socket", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown(ExecutorService acceptor) {
|
||||||
|
if (acceptor != null) {
|
||||||
|
acceptor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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.hooks;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
class Sockets {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Sockets.class);
|
||||||
|
|
||||||
|
private static final int READ_LIMIT = 8192;
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private Sockets() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send(OutputStream out, Object object) throws IOException {
|
||||||
|
byte[] bytes = objectMapper.writeValueAsBytes(object);
|
||||||
|
LOG.trace("send message length of {} to socket", bytes.length);
|
||||||
|
|
||||||
|
DataOutputStream dataOutputStream = new DataOutputStream(out);
|
||||||
|
dataOutputStream.writeInt(bytes.length);
|
||||||
|
|
||||||
|
LOG.trace("send message to socket");
|
||||||
|
dataOutputStream.write(bytes);
|
||||||
|
|
||||||
|
LOG.trace("flush socket");
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> T receive(InputStream in, Class<T> type) throws IOException {
|
||||||
|
LOG.trace("read {} from socket", type);
|
||||||
|
|
||||||
|
DataInputStream dataInputStream = new DataInputStream(in);
|
||||||
|
|
||||||
|
int length = dataInputStream.readInt();
|
||||||
|
LOG.trace("read message length of {} from socket", length);
|
||||||
|
if (length > READ_LIMIT) {
|
||||||
|
String message = String.format("received length of %d, which exceeds the limit of %d", length, READ_LIMIT);
|
||||||
|
throw new IOException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
dataInputStream.readFully(data);
|
||||||
|
|
||||||
|
LOG.trace("convert message to {}", type);
|
||||||
|
return objectMapper.readValue(data, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -27,7 +27,6 @@ package sonia.scm.repository.spi;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.aragost.javahg.Changeset;
|
import com.aragost.javahg.Changeset;
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
|
|
||||||
@@ -63,14 +62,8 @@ public class HgBranchesCommand extends AbstractCommand
|
|||||||
List<com.aragost.javahg.Branch> hgBranches =
|
List<com.aragost.javahg.Branch> hgBranches =
|
||||||
com.aragost.javahg.commands.BranchesCommand.on(open()).execute();
|
com.aragost.javahg.commands.BranchesCommand.on(open()).execute();
|
||||||
|
|
||||||
List<Branch> branches = Lists.transform(hgBranches,
|
return Lists.transform(hgBranches,
|
||||||
new Function<com.aragost.javahg.Branch,
|
hgBranch -> {
|
||||||
Branch>()
|
|
||||||
{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Branch apply(com.aragost.javahg.Branch hgBranch)
|
|
||||||
{
|
|
||||||
String node = null;
|
String node = null;
|
||||||
Changeset changeset = hgBranch.getBranchTip();
|
Changeset changeset = hgBranch.getBranchTip();
|
||||||
|
|
||||||
@@ -79,14 +72,12 @@ public class HgBranchesCommand extends AbstractCommand
|
|||||||
node = changeset.getNode();
|
node = changeset.getNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long lastCommitDate = changeset.getTimestamp().getDate().getTime();
|
||||||
if (DEFAULT_BRANCH_NAME.equals(hgBranch.getName())) {
|
if (DEFAULT_BRANCH_NAME.equals(hgBranch.getName())) {
|
||||||
return Branch.defaultBranch(hgBranch.getName(), node);
|
return Branch.defaultBranch(hgBranch.getName(), node, lastCommitDate);
|
||||||
} else {
|
} else {
|
||||||
return Branch.normalBranch(hgBranch.getName(), node);
|
return Branch.normalBranch(hgBranch.getName(), node, lastCommitDate);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return branches;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,18 +27,12 @@ package sonia.scm.repository.spi;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryProvider;
|
import sonia.scm.repository.RepositoryProvider;
|
||||||
import sonia.scm.web.HgUtil;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -46,105 +40,32 @@ import java.util.function.BiConsumer;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgCommandContext implements Closeable, RepositoryProvider
|
public class HgCommandContext implements Closeable, RepositoryProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
private final HgRepositoryHandler handler;
|
||||||
private static final String PROPERTY_ENCODING = "hg.encoding";
|
private final HgRepositoryFactory factory;
|
||||||
|
private final sonia.scm.repository.Repository scmRepository;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private Repository repository;
|
||||||
|
|
||||||
/**
|
public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param hookManager
|
|
||||||
* @param handler
|
|
||||||
* @param repository
|
|
||||||
* @param directory
|
|
||||||
*/
|
|
||||||
public HgCommandContext(HgHookManager hookManager,
|
|
||||||
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
|
|
||||||
File directory)
|
|
||||||
{
|
|
||||||
this(hookManager, handler, repository, directory,
|
|
||||||
handler.getHgContext().isPending());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param hookManager
|
|
||||||
* @param handler
|
|
||||||
* @param repository
|
|
||||||
* @param directory
|
|
||||||
* @param pending
|
|
||||||
*/
|
|
||||||
public HgCommandContext(HgHookManager hookManager,
|
|
||||||
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
|
|
||||||
File directory, boolean pending)
|
|
||||||
{
|
|
||||||
this.hookManager = hookManager;
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.directory = directory;
|
this.factory = factory;
|
||||||
this.scmRepository = repository;
|
this.scmRepository = scmRepository;
|
||||||
this.encoding = repository.getProperty(PROPERTY_ENCODING);
|
|
||||||
this.pending = pending;
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(encoding))
|
|
||||||
{
|
|
||||||
encoding = handler.getConfig().getEncoding();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
public Repository open() {
|
||||||
|
if (repository == null) {
|
||||||
/**
|
repository = factory.openForRead(scmRepository);
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
if (repository != null)
|
|
||||||
{
|
|
||||||
repository.close();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Repository open()
|
|
||||||
{
|
|
||||||
if (repository == null)
|
|
||||||
{
|
|
||||||
repository = HgUtil.open(handler, hookManager, directory, encoding, pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Repository openWithSpecialEnvironment(BiConsumer<sonia.scm.repository.Repository, Map<String, String>> prepareEnvironment)
|
public Repository openForWrite() {
|
||||||
{
|
return factory.openForWrite(scmRepository);
|
||||||
return HgUtil.open(handler, directory, encoding,
|
|
||||||
pending, environment -> prepareEnvironment.accept(scmRepository, environment));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public HgConfig getConfig()
|
public HgConfig getConfig()
|
||||||
{
|
{
|
||||||
return handler.getConfig();
|
return handler.getConfig();
|
||||||
@@ -159,25 +80,12 @@ public class HgCommandContext implements Closeable, RepositoryProvider
|
|||||||
return getScmRepository();
|
return getScmRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
@Override
|
||||||
private File directory;
|
public void close() {
|
||||||
|
if (repository != null) {
|
||||||
|
repository.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String encoding;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgRepositoryHandler handler;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgHookManager hookManager;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private boolean pending;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Repository repository;
|
|
||||||
|
|
||||||
private final sonia.scm.repository.Repository scmRepository;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,82 +24,53 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
|
||||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||||
import sonia.scm.web.HgUtil;
|
import sonia.scm.web.HgUtil;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgHookChangesetProvider implements HookChangesetProvider
|
public class HgHookChangesetProvider implements HookChangesetProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
private static final Logger LOG = LoggerFactory.getLogger(HgHookChangesetProvider.class);
|
||||||
* the logger for HgHookChangesetProvider
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(HgHookChangesetProvider.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private final HgRepositoryHandler handler;
|
||||||
|
private final HgRepositoryFactory factory;
|
||||||
|
private final sonia.scm.repository.Repository scmRepository;
|
||||||
|
private final String startRev;
|
||||||
|
|
||||||
public HgHookChangesetProvider(HgRepositoryHandler handler,
|
private HookChangesetResponse response;
|
||||||
File repositoryDirectory, HgHookManager hookManager, String startRev,
|
|
||||||
RepositoryHookType type)
|
public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
|
||||||
{
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.repositoryDirectory = repositoryDirectory;
|
this.factory = factory;
|
||||||
this.hookManager = hookManager;
|
this.scmRepository = scmRepository;
|
||||||
this.startRev = startRev;
|
this.startRev = startRev;
|
||||||
this.type = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request)
|
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
|
||||||
{
|
if (response == null) {
|
||||||
if (response == null)
|
|
||||||
{
|
|
||||||
Repository repository = null;
|
Repository repository = null;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
repository = factory.openForRead(scmRepository);
|
||||||
repository = open();
|
|
||||||
|
|
||||||
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository,
|
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig());
|
||||||
handler.getConfig());
|
|
||||||
|
|
||||||
response = new HookChangesetResponse(
|
response = new HookChangesetResponse(
|
||||||
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute());
|
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()
|
||||||
}
|
);
|
||||||
catch (Exception ex)
|
} catch (Exception ex) {
|
||||||
{
|
LOG.error("could not retrieve changesets", ex);
|
||||||
logger.error("could not retrieve changesets", ex);
|
} finally {
|
||||||
}
|
if (repository != null) {
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (repository != null)
|
|
||||||
{
|
|
||||||
repository.close();
|
repository.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,39 +79,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Repository open()
|
|
||||||
{
|
|
||||||
// use HG_PENDING only for pre receive hooks
|
|
||||||
boolean pending = type == RepositoryHookType.PRE_RECEIVE;
|
|
||||||
|
|
||||||
// TODO get repository encoding
|
|
||||||
return HgUtil.open(handler, hookManager, repositoryDirectory, null,
|
|
||||||
pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgRepositoryHandler handler;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgHookManager hookManager;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private File repositoryDirectory;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HookChangesetResponse response;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String startRev;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private RepositoryHookType type;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.HgHookBranchProvider;
|
import sonia.scm.repository.api.HgHookBranchProvider;
|
||||||
import sonia.scm.repository.api.HgHookMessageProvider;
|
import sonia.scm.repository.api.HgHookMessageProvider;
|
||||||
import sonia.scm.repository.api.HgHookTagProvider;
|
import sonia.scm.repository.api.HgHookTagProvider;
|
||||||
@@ -37,7 +37,6 @@ import sonia.scm.repository.api.HookFeature;
|
|||||||
import sonia.scm.repository.api.HookMessageProvider;
|
import sonia.scm.repository.api.HookMessageProvider;
|
||||||
import sonia.scm.repository.api.HookTagProvider;
|
import sonia.scm.repository.api.HookTagProvider;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -48,52 +47,37 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgHookContextProvider extends HookContextProvider
|
public class HgHookContextProvider extends HookContextProvider {
|
||||||
{
|
|
||||||
|
|
||||||
private static final Set<HookFeature> SUPPORTED_FEATURES =
|
private static final Set<HookFeature> SUPPORTED_FEATURES = EnumSet.of(
|
||||||
EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER,
|
HookFeature.CHANGESET_PROVIDER,
|
||||||
HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
HookFeature.MESSAGE_PROVIDER,
|
||||||
|
HookFeature.BRANCH_PROVIDER,
|
||||||
|
HookFeature.TAG_PROVIDER
|
||||||
|
);
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private final HgHookChangesetProvider hookChangesetProvider;
|
||||||
|
private HgHookMessageProvider hgMessageProvider;
|
||||||
|
private HgHookBranchProvider hookBranchProvider;
|
||||||
|
private HgHookTagProvider hookTagProvider;
|
||||||
|
|
||||||
/**
|
public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) {
|
||||||
* Constructs a new instance.
|
this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev);
|
||||||
*
|
|
||||||
* @param handler mercurial repository handler
|
|
||||||
* @param repositoryDirectory the directory of the changed repository
|
|
||||||
* @param hookManager mercurial hook manager
|
|
||||||
* @param startRev start revision
|
|
||||||
* @param type type of hook
|
|
||||||
*/
|
|
||||||
public HgHookContextProvider(HgRepositoryHandler handler,
|
|
||||||
File repositoryDirectory, HgHookManager hookManager, String startRev,
|
|
||||||
RepositoryHookType type)
|
|
||||||
{
|
|
||||||
this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HookBranchProvider getBranchProvider()
|
public HookBranchProvider getBranchProvider() {
|
||||||
{
|
if (hookBranchProvider == null) {
|
||||||
if (hookBranchProvider == null)
|
|
||||||
{
|
|
||||||
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
|
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hookBranchProvider;
|
return hookBranchProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HookTagProvider getTagProvider()
|
public HookTagProvider getTagProvider() {
|
||||||
{
|
if (hookTagProvider == null) {
|
||||||
if (hookTagProvider == null)
|
|
||||||
{
|
|
||||||
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
|
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hookTagProvider;
|
return hookTagProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +87,10 @@ public class HgHookContextProvider extends HookContextProvider
|
|||||||
return hookChangesetProvider;
|
return hookChangesetProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HgHookMessageProvider getHgMessageProvider()
|
public HgHookMessageProvider getHgMessageProvider() {
|
||||||
{
|
if (hgMessageProvider == null) {
|
||||||
if (hgMessageProvider == null)
|
|
||||||
{
|
|
||||||
hgMessageProvider = new HgHookMessageProvider();
|
hgMessageProvider = new HgHookMessageProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
return hgMessageProvider;
|
return hgMessageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,21 +100,9 @@ public class HgHookContextProvider extends HookContextProvider
|
|||||||
return SUPPORTED_FEATURES;
|
return SUPPORTED_FEATURES;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HookMessageProvider createMessageProvider()
|
protected HookMessageProvider createMessageProvider()
|
||||||
{
|
{
|
||||||
return getHgMessageProvider();
|
return getHgMessageProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
private final HgHookChangesetProvider hookChangesetProvider;
|
|
||||||
|
|
||||||
private HgHookMessageProvider hgMessageProvider;
|
|
||||||
|
|
||||||
private HgHookBranchProvider hookBranchProvider;
|
|
||||||
|
|
||||||
private HgHookTagProvider hookTagProvider;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import com.aragost.javahg.commands.ExecutionException;
|
|||||||
import com.aragost.javahg.commands.PullCommand;
|
import com.aragost.javahg.commands.PullCommand;
|
||||||
import com.aragost.javahg.commands.RemoveCommand;
|
import com.aragost.javahg.commands.RemoveCommand;
|
||||||
import com.aragost.javahg.commands.StatusCommand;
|
import com.aragost.javahg.commands.StatusCommand;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.NoChangesMadeException;
|
import sonia.scm.NoChangesMadeException;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.work.WorkingCopy;
|
import sonia.scm.repository.work.WorkingCopy;
|
||||||
@@ -41,11 +43,13 @@ import java.nio.file.Path;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S3252") // it is ok for javahg classes to access static method of subtype
|
||||||
public class HgModifyCommand implements ModifyCommand {
|
public class HgModifyCommand implements ModifyCommand {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HgModifyCommand.class);
|
||||||
static final Pattern HG_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\](?: Error:)? (.*)");
|
static final Pattern HG_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\](?: Error:)? (.*)");
|
||||||
|
|
||||||
private HgCommandContext context;
|
private final HgCommandContext context;
|
||||||
private final HgWorkingCopyFactory workingCopyFactory;
|
private final HgWorkingCopyFactory workingCopyFactory;
|
||||||
|
|
||||||
public HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
|
public HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
|
||||||
@@ -55,7 +59,6 @@ public class HgModifyCommand implements ModifyCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String execute(ModifyCommandRequest request) {
|
public String execute(ModifyCommandRequest request) {
|
||||||
|
|
||||||
try (WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy = workingCopyFactory.createWorkingCopy(context, request.getBranch())) {
|
try (WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy = workingCopyFactory.createWorkingCopy(context, request.getBranch())) {
|
||||||
Repository workingRepository = workingCopy.getWorkingRepository();
|
Repository workingRepository = workingCopy.getWorkingRepository();
|
||||||
request.getRequests().forEach(
|
request.getRequests().forEach(
|
||||||
@@ -100,12 +103,21 @@ public class HgModifyCommand implements ModifyCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (StatusCommand.on(workingRepository).lines().isEmpty()) {
|
if (StatusCommand.on(workingRepository).lines().isEmpty()) {
|
||||||
throw new NoChangesMadeException(context.getScmRepository());
|
throw new NoChangesMadeException(context.getScmRepository());
|
||||||
}
|
}
|
||||||
CommitCommand.on(workingRepository).user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail())).message(request.getCommitMessage()).execute();
|
|
||||||
|
LOG.trace("commit changes in working copy");
|
||||||
|
CommitCommand.on(workingRepository)
|
||||||
|
.user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail()))
|
||||||
|
.message(request.getCommitMessage()).execute();
|
||||||
|
|
||||||
List<Changeset> execute = pullModifyChangesToCentralRepository(request, workingCopy);
|
List<Changeset> execute = pullModifyChangesToCentralRepository(request, workingCopy);
|
||||||
return execute.get(0).getNode();
|
|
||||||
|
String node = execute.get(0).getNode();
|
||||||
|
LOG.debug("successfully pulled changes from working copy, new node {}", node);
|
||||||
|
return node;
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
throwInternalRepositoryException("could not execute command on repository", e);
|
throwInternalRepositoryException("could not execute command on repository", e);
|
||||||
return null;
|
return null;
|
||||||
@@ -113,6 +125,7 @@ public class HgModifyCommand implements ModifyCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Changeset> pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy) {
|
private List<Changeset> pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy) {
|
||||||
|
LOG.trace("pull changes from working copy");
|
||||||
try {
|
try {
|
||||||
com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
|
com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
|
||||||
workingCopyFactory.configure(pullCommand);
|
workingCopyFactory.configure(pullCommand);
|
||||||
|
|||||||
@@ -26,13 +26,12 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import com.google.common.io.Closeables;
|
import com.google.common.io.Closeables;
|
||||||
import sonia.scm.repository.Feature;
|
import sonia.scm.repository.Feature;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -41,11 +40,8 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
//J-
|
|
||||||
public static final Set<Command> COMMANDS = EnumSet.of(
|
public static final Set<Command> COMMANDS = EnumSet.of(
|
||||||
Command.BLAME,
|
Command.BLAME,
|
||||||
Command.BROWSE,
|
Command.BROWSE,
|
||||||
@@ -61,25 +57,19 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
Command.PULL,
|
Command.PULL,
|
||||||
Command.MODIFY
|
Command.MODIFY
|
||||||
);
|
);
|
||||||
//J+
|
|
||||||
|
|
||||||
/** Field description */
|
public static final Set<Feature> FEATURES = EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
|
||||||
public static final Set<Feature> FEATURES =
|
|
||||||
EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private final HgRepositoryHandler handler;
|
||||||
|
private final HgCommandContext context;
|
||||||
|
|
||||||
HgRepositoryServiceProvider(HgRepositoryHandler handler,
|
HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) {
|
||||||
HgHookManager hookManager, Repository repository)
|
|
||||||
{
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.repositoryDirectory = handler.getDirectory(repository.getId());
|
this.context = new HgCommandContext(handler, factory, repository);
|
||||||
this.context = new HgCommandContext(hookManager, handler, repository,
|
|
||||||
repositoryDirectory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -91,9 +81,9 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
{
|
{
|
||||||
Closeables.close(context, true);
|
Closeables.close(context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
@@ -35,18 +35,15 @@ import sonia.scm.repository.Repository;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Extension
|
@Extension
|
||||||
public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||||
{
|
|
||||||
|
|
||||||
private final HgRepositoryHandler handler;
|
private final HgRepositoryHandler handler;
|
||||||
private final HgHookManager hookManager;
|
private final HgRepositoryFactory factory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
|
public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) {
|
||||||
HgHookManager hookManager)
|
|
||||||
{
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.hookManager = hookManager;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,7 +51,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
|||||||
HgRepositoryServiceProvider provider = null;
|
HgRepositoryServiceProvider provider = null;
|
||||||
|
|
||||||
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||||
provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
|
provider = new HgRepositoryServiceProvider(handler, factory, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* 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.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.HgConfig;
|
||||||
|
import sonia.scm.repository.HgVersion;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HgVersionCommand {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HgVersionCommand.class);
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String[] HG_ARGS = {
|
||||||
|
"version", "--template", "{ver}"
|
||||||
|
};
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String[] PYTHON_ARGS = {
|
||||||
|
"-c", "import sys; print(sys.version)"
|
||||||
|
};
|
||||||
|
|
||||||
|
private final HgConfig config;
|
||||||
|
private final ProcessExecutor executor;
|
||||||
|
|
||||||
|
public HgVersionCommand(HgConfig config) {
|
||||||
|
this(config, command -> new ProcessBuilder(command).start());
|
||||||
|
}
|
||||||
|
|
||||||
|
HgVersionCommand(HgConfig config, ProcessExecutor executor) {
|
||||||
|
this.config = config;
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HgVersion get() {
|
||||||
|
return new HgVersion(getHgVersion(), getPythonVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private String getPythonVersion() {
|
||||||
|
try {
|
||||||
|
String content = exec(config.getPythonBinary(), PYTHON_ARGS);
|
||||||
|
int index = content.indexOf(' ');
|
||||||
|
if (index > 0) {
|
||||||
|
return content.substring(0, index);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.warn("failed to get python version", ex);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
LOG.warn("failed to get python version", ex);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return HgVersion.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private String getHgVersion() {
|
||||||
|
try {
|
||||||
|
return exec(config.getHgBinary(), HG_ARGS).trim();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.warn("failed to get mercurial version", ex);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
LOG.warn("failed to get mercurial version", ex);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return HgVersion.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
private String exec(String command, String[] args) throws IOException, InterruptedException {
|
||||||
|
List<String> cmd = new ArrayList<>();
|
||||||
|
cmd.add(command);
|
||||||
|
cmd.addAll(Arrays.asList(args));
|
||||||
|
|
||||||
|
Process process = executor.execute(cmd);
|
||||||
|
byte[] bytes = ByteStreams.toByteArray(process.getInputStream());
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw new IOException("process ends with exit code " + exitCode);
|
||||||
|
}
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ProcessExecutor {
|
||||||
|
Process execute(List<String> command) throws IOException;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,27 +36,21 @@ import sonia.scm.repository.InternalRepositoryException;
|
|||||||
import sonia.scm.repository.work.SimpleWorkingCopyFactory;
|
import sonia.scm.repository.work.SimpleWorkingCopyFactory;
|
||||||
import sonia.scm.repository.work.WorkingCopyPool;
|
import sonia.scm.repository.work.WorkingCopyPool;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Repository, Repository, HgCommandContext> implements HgWorkingCopyFactory {
|
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Repository, Repository, HgCommandContext> implements HgWorkingCopyFactory {
|
||||||
|
|
||||||
private final Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SimpleHgWorkingCopyFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) {
|
public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) {
|
||||||
super(workdirProvider);
|
super(workdirProvider);
|
||||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParentAndClone<Repository, Repository> initialize(HgCommandContext context, File target, String initialBranch) {
|
public ParentAndClone<Repository, Repository> initialize(HgCommandContext context, File target, String initialBranch) {
|
||||||
Repository centralRepository = openCentral(context);
|
Repository centralRepository = context.openForWrite();
|
||||||
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
|
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
|
||||||
if (initialBranch != null) {
|
if (initialBranch != null) {
|
||||||
cloneCommand.updaterev(initialBranch);
|
cloneCommand.updaterev(initialBranch);
|
||||||
@@ -76,7 +70,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
|||||||
// The hg api to create a command is meant to be used from the command classes, not from their "flags" base classes.
|
// The hg api to create a command is meant to be used from the command classes, not from their "flags" base classes.
|
||||||
@SuppressWarnings("java:S3252")
|
@SuppressWarnings("java:S3252")
|
||||||
protected ParentAndClone<Repository, Repository> reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
|
protected ParentAndClone<Repository, Repository> reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
|
||||||
Repository centralRepository = openCentral(context);
|
Repository centralRepository = context.openForWrite();
|
||||||
try {
|
try {
|
||||||
BaseRepository clone = Repository.open(target);
|
BaseRepository clone = Repository.open(target);
|
||||||
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
|
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
|
||||||
@@ -89,12 +83,6 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Repository openCentral(HgCommandContext context) {
|
|
||||||
BiConsumer<sonia.scm.repository.Repository, Map<String, String>> repositoryMapBiConsumer =
|
|
||||||
(repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment);
|
|
||||||
return context.openWithSpecialEnvironment(repositoryMapBiConsumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void delete(File directory, String unknownFile) throws IOException {
|
private void delete(File directory, String unknownFile) throws IOException {
|
||||||
IOUtil.delete(new File(directory, unknownFile));
|
IOUtil.delete(new File(directory, unknownFile));
|
||||||
}
|
}
|
||||||
@@ -111,7 +99,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(PullCommand pullCommand) {
|
public void configure(PullCommand pullCommand) {
|
||||||
pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook");
|
pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.post_hook");
|
||||||
pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook");
|
pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.pre_hook");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
|
import sonia.scm.repository.HgEnvironmentBuilder;
|
||||||
import sonia.scm.repository.HgPythonScript;
|
import sonia.scm.repository.HgPythonScript;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
@@ -42,29 +43,21 @@ import sonia.scm.web.cgi.CGIExecutor;
|
|||||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||||
import sonia.scm.web.cgi.EnvList;
|
import sonia.scm.web.cgi.EnvList;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import java.util.Enumeration;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final long serialVersionUID = -3492811300905099810L;
|
private static final long serialVersionUID = -3492811300905099810L;
|
||||||
@@ -80,13 +73,13 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
ScmConfiguration configuration,
|
ScmConfiguration configuration,
|
||||||
HgRepositoryHandler handler,
|
HgRepositoryHandler handler,
|
||||||
RepositoryRequestListenerUtil requestListenerUtil,
|
RepositoryRequestListenerUtil requestListenerUtil,
|
||||||
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder)
|
HgEnvironmentBuilder environmentBuilder)
|
||||||
{
|
{
|
||||||
this.cgiExecutorFactory = cgiExecutorFactory;
|
this.cgiExecutorFactory = cgiExecutorFactory;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.requestListenerUtil = requestListenerUtil;
|
this.requestListenerUtil = requestListenerUtil;
|
||||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
this.environmentBuilder = environmentBuilder;
|
||||||
this.exceptionHandler = new HgCGIExceptionHandler();
|
this.exceptionHandler = new HgCGIExceptionHandler();
|
||||||
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
||||||
}
|
}
|
||||||
@@ -108,11 +101,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
{
|
{
|
||||||
handleRequest(request, response, repository);
|
handleRequest(request, response, repository);
|
||||||
}
|
}
|
||||||
catch (ServletException ex)
|
catch (ServletException | IOException ex)
|
||||||
{
|
|
||||||
exceptionHandler.handleException(request, response, ex);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
{
|
||||||
exceptionHandler.handleException(request, response, ex);
|
exceptionHandler.handleException(request, response, ex);
|
||||||
}
|
}
|
||||||
@@ -146,29 +135,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param env
|
|
||||||
* @param session
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void passSessionAttributes(EnvList env, HttpSession session)
|
|
||||||
{
|
|
||||||
Enumeration<String> enm = session.getAttributeNames();
|
|
||||||
|
|
||||||
while (enm.hasMoreElements())
|
|
||||||
{
|
|
||||||
String key = enm.nextElement();
|
|
||||||
|
|
||||||
if (key.startsWith(ENV_SESSION_PREFIX))
|
|
||||||
{
|
|
||||||
env.set(key, session.getAttribute(key).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -192,7 +158,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
executor.setExceptionHandler(exceptionHandler);
|
executor.setExceptionHandler(exceptionHandler);
|
||||||
executor.setStatusCodeHandler(exceptionHandler);
|
executor.setStatusCodeHandler(exceptionHandler);
|
||||||
executor.setContentLengthWorkaround(true);
|
executor.setContentLengthWorkaround(true);
|
||||||
hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
|
|
||||||
|
EnvList env = executor.getEnvironment();
|
||||||
|
environmentBuilder.write(repository).forEach(env::set);
|
||||||
|
|
||||||
String interpreter = getInterpreter();
|
String interpreter = getInterpreter();
|
||||||
|
|
||||||
@@ -248,5 +216,5 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
private final RepositoryRequestListenerUtil requestListenerUtil;
|
private final RepositoryRequestListenerUtil requestListenerUtil;
|
||||||
|
|
||||||
private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder;
|
private final HgEnvironmentBuilder environmentBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,446 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.web;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.io.Closeables;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.NotFoundException;
|
|
||||||
import sonia.scm.repository.HgContext;
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
|
||||||
import sonia.scm.repository.api.HgHookMessage;
|
|
||||||
import sonia.scm.repository.api.HgHookMessage.Severity;
|
|
||||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
|
||||||
import sonia.scm.repository.spi.HookEventFacade;
|
|
||||||
import sonia.scm.security.BearerToken;
|
|
||||||
import sonia.scm.security.CipherUtil;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
import sonia.scm.util.Util;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class HgHookCallbackServlet extends HttpServlet
|
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String HGHOOK_POST_RECEIVE = "changegroup";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PARAM_REPOSITORYID = "repositoryId";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String PARAM_CHALLENGE = "challenge";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String PARAM_TOKEN = "token";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String PARAM_NODE = "node";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String PARAM_PING = "ping";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final Pattern REGEX_URL =
|
|
||||||
Pattern.compile("^/hook/hg/([^/]+)$");
|
|
||||||
|
|
||||||
/** the logger for HgHookCallbackServlet */
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(HgHookCallbackServlet.class);
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final long serialVersionUID = 3531596724828189353L;
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public HgHookCallbackServlet(HookEventFacade hookEventFacade,
|
|
||||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
|
||||||
Provider<HgContext> contextProvider)
|
|
||||||
{
|
|
||||||
this.hookEventFacade = hookEventFacade;
|
|
||||||
this.handler = handler;
|
|
||||||
this.hookManager = hookManager;
|
|
||||||
this.contextProvider = contextProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param response
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
{
|
|
||||||
String ping = request.getParameter(PARAM_PING);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(ping) && Boolean.parseBoolean(ping))
|
|
||||||
{
|
|
||||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
||||||
try {
|
|
||||||
handlePostRequest(request, response);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logger.warn("error in hook callback execution, sending internal server error", ex);
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
String strippedURI = HttpUtil.getStrippedURI(request);
|
|
||||||
Matcher m = REGEX_URL.matcher(strippedURI);
|
|
||||||
|
|
||||||
if (m.matches())
|
|
||||||
{
|
|
||||||
String repositoryId = getRepositoryId(request);
|
|
||||||
String type = m.group(1);
|
|
||||||
String challenge = request.getParameter(PARAM_CHALLENGE);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(challenge))
|
|
||||||
{
|
|
||||||
String node = request.getParameter(PARAM_NODE);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(node))
|
|
||||||
{
|
|
||||||
String token = request.getParameter(PARAM_TOKEN);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(token))
|
|
||||||
{
|
|
||||||
authenticate(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
hookCallback(response, type, repositoryId, challenge, node);
|
|
||||||
}
|
|
||||||
else if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("node parameter not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("challenge parameter not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("url does not match");
|
|
||||||
}
|
|
||||||
|
|
||||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void authenticate(String token)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
token = CipherUtil.getInstance().decode(token);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(token))
|
|
||||||
{
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
|
|
||||||
AuthenticationToken accessToken = createToken(token);
|
|
||||||
|
|
||||||
//J-
|
|
||||||
subject.login(accessToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.error("could not authenticate user", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthenticationToken createToken(String tokenString)
|
|
||||||
{
|
|
||||||
return BearerToken.valueOf(tokenString);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
HgHookContextProvider context = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (type == RepositoryHookType.PRE_RECEIVE)
|
|
||||||
{
|
|
||||||
contextProvider.get().setPending(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
File repositoryDirectory = handler.getDirectory(repositoryId);
|
|
||||||
context = new HgHookContextProvider(handler, repositoryDirectory, hookManager,
|
|
||||||
node, type);
|
|
||||||
|
|
||||||
hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
|
|
||||||
|
|
||||||
printMessages(response, context);
|
|
||||||
}
|
|
||||||
catch (NotFoundException ex)
|
|
||||||
{
|
|
||||||
logger.error(ex.getMessage());
|
|
||||||
|
|
||||||
logger.trace("repository not found", ex);
|
|
||||||
|
|
||||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
sendError(response, context, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hookCallback(HttpServletResponse response, String typeName, String repositoryId, String challenge, String node) throws IOException {
|
|
||||||
if (hookManager.isAcceptAble(challenge))
|
|
||||||
{
|
|
||||||
RepositoryHookType type = null;
|
|
||||||
|
|
||||||
if (HGHOOK_PRE_RECEIVE.equals(typeName))
|
|
||||||
{
|
|
||||||
type = RepositoryHookType.PRE_RECEIVE;
|
|
||||||
}
|
|
||||||
else if (HGHOOK_POST_RECEIVE.equals(typeName))
|
|
||||||
{
|
|
||||||
type = RepositoryHookType.POST_RECEIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type != null)
|
|
||||||
{
|
|
||||||
fireHook(response, repositoryId, node, type);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (logger.isWarnEnabled())
|
|
||||||
{
|
|
||||||
logger.warn("unknown hook type {}", typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (logger.isWarnEnabled())
|
|
||||||
{
|
|
||||||
logger.warn("hg hook challenge is not accept able");
|
|
||||||
}
|
|
||||||
|
|
||||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param writer
|
|
||||||
* @param msg
|
|
||||||
*/
|
|
||||||
private void printMessage(PrintWriter writer, HgHookMessage msg)
|
|
||||||
{
|
|
||||||
writer.append('_');
|
|
||||||
|
|
||||||
if (msg.getSeverity() == Severity.ERROR)
|
|
||||||
{
|
|
||||||
writer.append("e[SCM] Error: ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
writer.append("n[SCM] ");
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.println(msg.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param response
|
|
||||||
* @param context
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private void printMessages(HttpServletResponse response,
|
|
||||||
HgHookContextProvider context)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
List<HgHookMessage> msgs = context.getHgMessageProvider().getMessages();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(msgs))
|
|
||||||
{
|
|
||||||
PrintWriter writer = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
writer = response.getWriter();
|
|
||||||
|
|
||||||
printMessages(writer, msgs);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Closeables.close(writer, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param writer
|
|
||||||
* @param msgs
|
|
||||||
*/
|
|
||||||
private void printMessages(PrintWriter writer, List<HgHookMessage> msgs)
|
|
||||||
{
|
|
||||||
for (HgHookMessage msg : msgs)
|
|
||||||
{
|
|
||||||
printMessage(writer, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param response
|
|
||||||
* @param context
|
|
||||||
* @param ex
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private void sendError(HttpServletResponse response,
|
|
||||||
HgHookContextProvider context, Exception ex)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
logger.warn("hook ended with exception", ex);
|
|
||||||
response.setStatus(HttpServletResponse.SC_CONFLICT);
|
|
||||||
|
|
||||||
String msg = ex.getMessage();
|
|
||||||
List<HgHookMessage> msgs = null;
|
|
||||||
|
|
||||||
if (context != null)
|
|
||||||
{
|
|
||||||
msgs = context.getHgMessageProvider().getMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(msg) || Util.isNotEmpty(msgs))
|
|
||||||
{
|
|
||||||
PrintWriter writer = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
writer = response.getWriter();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(msgs))
|
|
||||||
{
|
|
||||||
printMessages(writer, msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(msg))
|
|
||||||
{
|
|
||||||
printMessage(writer, new HgHookMessage(Severity.ERROR, msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Closeables.close(writer, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
private String getRepositoryId(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String id = request.getParameter(PARAM_REPOSITORYID);
|
|
||||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "repository id not found in request");
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final Provider<HgContext> contextProvider;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final HgRepositoryHandler handler;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final HookEventFacade hookEventFacade;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final HgHookManager hookManager;
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.web;
|
|
||||||
|
|
||||||
import sonia.scm.repository.HgEnvironment;
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class HgRepositoryEnvironmentBuilder {
|
|
||||||
|
|
||||||
private static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
|
||||||
private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
|
||||||
private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
|
||||||
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
|
|
||||||
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
|
||||||
|
|
||||||
private final HgRepositoryHandler handler;
|
|
||||||
private final HgHookManager hookManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) {
|
|
||||||
this.handler = handler;
|
|
||||||
this.hookManager = hookManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void buildFor(Repository repository, HttpServletRequest request, Map<String, String> environment) {
|
|
||||||
File directory = handler.getDirectory(repository.getId());
|
|
||||||
|
|
||||||
environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
|
|
||||||
environment.put(ENV_REPOSITORY_ID, repository.getId());
|
|
||||||
environment.put(ENV_REPOSITORY_PATH,
|
|
||||||
directory.getAbsolutePath());
|
|
||||||
|
|
||||||
// add hook environment
|
|
||||||
if (handler.getConfig().isDisableHookSSLValidation()) {
|
|
||||||
// disable ssl validation
|
|
||||||
// Issue 959: https://goo.gl/zH5eY8
|
|
||||||
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable experimental httppostargs protocol of mercurial
|
|
||||||
// Issue 970: https://goo.gl/poascp
|
|
||||||
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
|
|
||||||
|
|
||||||
HgEnvironment.prepareEnvironment(
|
|
||||||
environment,
|
|
||||||
handler,
|
|
||||||
hookManager,
|
|
||||||
request
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,9 +34,6 @@ import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper;
|
|||||||
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
|
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
|
||||||
import sonia.scm.installer.HgPackageReader;
|
import sonia.scm.installer.HgPackageReader;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.HgContext;
|
|
||||||
import sonia.scm.repository.HgContextProvider;
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
||||||
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
||||||
|
|
||||||
@@ -45,26 +42,10 @@ import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Extension
|
@Extension
|
||||||
public class HgServletModule extends ServletModule
|
public class HgServletModule extends ServletModule {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String MAPPING_HG = "/hg/*";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String MAPPING_HOOK = "/hook/hg/*";
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureServlets()
|
protected void configureServlets() {
|
||||||
{
|
|
||||||
bind(HgContext.class).toProvider(HgContextProvider.class);
|
|
||||||
bind(HgHookManager.class);
|
|
||||||
bind(HgPackageReader.class);
|
bind(HgPackageReader.class);
|
||||||
|
|
||||||
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
|
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
|
||||||
@@ -72,9 +53,6 @@ public class HgServletModule extends ServletModule
|
|||||||
bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass());
|
bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass());
|
||||||
bind(HgConfigInstallationsToDtoMapper.class);
|
bind(HgConfigInstallationsToDtoMapper.class);
|
||||||
|
|
||||||
// bind servlets
|
|
||||||
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
|
||||||
|
|
||||||
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
|
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,186 +24,45 @@
|
|||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
|
||||||
import com.aragost.javahg.RepositoryConfiguration;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.HgEnvironment;
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.HgPythonScript;
|
import sonia.scm.repository.HgPythonScript;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
|
||||||
import sonia.scm.repository.spi.javahg.HgFileviewExtension;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public final class HgUtil
|
public final class HgUtil {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String REVISION_TIP = "tip";
|
public static final String REVISION_TIP = "tip";
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String USERAGENT_HG = "mercurial/";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for HgUtil
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HgUtil.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private HgUtil() {}
|
private HgUtil() {}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
public static String getPythonPath(HgConfig config) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* @param hookManager
|
|
||||||
* @param directory
|
|
||||||
* @param encoding
|
|
||||||
* @param pending
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static Repository open(HgRepositoryHandler handler,
|
|
||||||
HgHookManager hookManager, File directory, String encoding, boolean pending)
|
|
||||||
{
|
|
||||||
return open(
|
|
||||||
handler,
|
|
||||||
directory,
|
|
||||||
encoding,
|
|
||||||
pending,
|
|
||||||
environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Repository open(HgRepositoryHandler handler,
|
|
||||||
File directory, String encoding, boolean pending,
|
|
||||||
Consumer<Map<String, String>> prepareEnvironment)
|
|
||||||
{
|
|
||||||
String enc = encoding;
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(enc))
|
|
||||||
{
|
|
||||||
enc = handler.getConfig().getEncoding();
|
|
||||||
}
|
|
||||||
|
|
||||||
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
|
||||||
|
|
||||||
prepareEnvironment.accept(repoConfiguration.getEnvironment());
|
|
||||||
|
|
||||||
repoConfiguration.addExtension(HgFileviewExtension.class);
|
|
||||||
repoConfiguration.setEnablePendingChangesets(pending);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Charset charset = Charset.forName(enc);
|
|
||||||
|
|
||||||
logger.trace("set encoding {} for mercurial", enc);
|
|
||||||
|
|
||||||
repoConfiguration.setEncoding(charset);
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex)
|
|
||||||
{
|
|
||||||
logger.error("could not set encoding for mercurial", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
|
|
||||||
|
|
||||||
logger.debug("open hg repository {}: encoding: {}, pending: {}", directory, enc, pending);
|
|
||||||
|
|
||||||
return Repository.open(repoConfiguration, directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getPythonPath(HgConfig config)
|
|
||||||
{
|
|
||||||
String pythonPath = Util.EMPTY_STRING;
|
String pythonPath = Util.EMPTY_STRING;
|
||||||
|
|
||||||
if (config != null)
|
if (config != null) {
|
||||||
{
|
|
||||||
pythonPath = Util.nonNull(config.getPythonPath());
|
pythonPath = Util.nonNull(config.getPythonPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.isNotEmpty(pythonPath))
|
if (Util.isNotEmpty(pythonPath)) {
|
||||||
{
|
|
||||||
pythonPath = pythonPath.concat(File.pathSeparator);
|
pythonPath = pythonPath.concat(File.pathSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
//J-
|
|
||||||
pythonPath = pythonPath.concat(
|
pythonPath = pythonPath.concat(
|
||||||
HgPythonScript.getScriptDirectory(
|
HgPythonScript.getScriptDirectory(
|
||||||
SCMContext.getContext()
|
SCMContext.getContext()
|
||||||
).getAbsolutePath()
|
).getAbsolutePath()
|
||||||
);
|
);
|
||||||
//J+
|
|
||||||
|
|
||||||
return pythonPath;
|
return pythonPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String getRevision(String revision) {
|
||||||
* Method description
|
return Util.isEmpty(revision) ? REVISION_TIP : revision;
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param revision
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getRevision(String revision)
|
|
||||||
{
|
|
||||||
return Util.isEmpty(revision)
|
|
||||||
? REVISION_TIP
|
|
||||||
: revision;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the request comes from a mercurial client.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request servlet request
|
|
||||||
*
|
|
||||||
* @return true if the client is mercurial
|
|
||||||
*/
|
|
||||||
public static boolean isHgClient(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
return HttpUtil.userAgentStartsWith(request, USERAGENT_HG);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ type Configuration = {
|
|||||||
encoding: string;
|
encoding: string;
|
||||||
useOptimizedBytecode: boolean;
|
useOptimizedBytecode: boolean;
|
||||||
showRevisionInId: boolean;
|
showRevisionInId: boolean;
|
||||||
disableHookSSLValidation: boolean;
|
|
||||||
enableHttpPostArgs: boolean;
|
enableHttpPostArgs: boolean;
|
||||||
_links: Links;
|
_links: Links;
|
||||||
};
|
};
|
||||||
@@ -139,7 +138,6 @@ class HgConfigurationForm extends React.Component<Props, State> {
|
|||||||
{this.checkbox("showRevisionInId")}
|
{this.checkbox("showRevisionInId")}
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-half">
|
<div className="column is-half">
|
||||||
{this.checkbox("disableHookSSLValidation")}
|
|
||||||
{this.checkbox("enableHttpPostArgs")}
|
{this.checkbox("enableHttpPostArgs")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,8 +25,6 @@
|
|||||||
"showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen.",
|
"showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen.",
|
||||||
"enableHttpPostArgs": "HttpPostArgs Protocol aktivieren",
|
"enableHttpPostArgs": "HttpPostArgs Protocol aktivieren",
|
||||||
"enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.",
|
"enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.",
|
||||||
"disableHookSSLValidation": "SSL Validierung für Hooks deaktivieren",
|
|
||||||
"disableHookSSLValidationHelpText": "Deaktiviert die Validierung von SSL Zertifikaten für den Mercurial Hook, der die Repositoryänderungen wieder zurück an den SCM-Manager leitet. Diese Option sollte nur benutzt werden, wenn der SCM-Manager ein selbstsigniertes Zertifikat verwendet.",
|
|
||||||
"disabled": "Deaktiviert",
|
"disabled": "Deaktiviert",
|
||||||
"disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin.",
|
"disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin.",
|
||||||
"required": "Dieser Konfigurationswert wird benötigt"
|
"required": "Dieser Konfigurationswert wird benötigt"
|
||||||
|
|||||||
@@ -25,8 +25,6 @@
|
|||||||
"showRevisionInIdHelpText": "Show revision as part of the node id.",
|
"showRevisionInIdHelpText": "Show revision as part of the node id.",
|
||||||
"enableHttpPostArgs": "Enable HttpPostArgs Protocol",
|
"enableHttpPostArgs": "Enable HttpPostArgs Protocol",
|
||||||
"enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
"enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
||||||
"disableHookSSLValidation": "Disable SSL Validation on Hooks",
|
|
||||||
"disableHookSSLValidationHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.",
|
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"disabledHelpText": "Enable or disable the Mercurial plugin.",
|
"disabledHelpText": "Enable or disable the Mercurial plugin.",
|
||||||
"required": "This configuration value is required"
|
"required": "This configuration value is required"
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ u.setconfig(b'web', b'push_ssl', b'false')
|
|||||||
u.setconfig(b'web', b'allow_read', b'*')
|
u.setconfig(b'web', b'allow_read', b'*')
|
||||||
u.setconfig(b'web', b'allow_push', b'*')
|
u.setconfig(b'web', b'allow_push', b'*')
|
||||||
|
|
||||||
u.setconfig(b'hooks', b'changegroup.scm', b'python:scmhooks.postHook')
|
u.setconfig(b'hooks', b'changegroup.scm', b'python:scmhooks.post_hook')
|
||||||
u.setconfig(b'hooks', b'pretxnchangegroup.scm', b'python:scmhooks.preHook')
|
u.setconfig(b'hooks', b'pretxnchangegroup.scm', b'python:scmhooks.pre_hook')
|
||||||
|
|
||||||
# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial
|
# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial
|
||||||
# SCM_HTTP_POST_ARGS is set by HgCGIServlet
|
# SCM_HTTP_POST_ARGS is set by HgCGIServlet
|
||||||
|
|||||||