diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54a64a7ebc..3f9ae53037 100644
--- a/CHANGELOG.md
+++ b/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).
## 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
- 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))
- 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))
+- Add examples to core resources to simplify usage of rest api ([#1434](https://github.com/scm-manager/scm-manager/pull/1434))
### Fixed
- 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.7.0]: https://www.scm-manager.org/download/2.7.0
[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
diff --git a/docs/de/user/profile/assets/api-key-overview.png b/docs/de/user/profile/assets/api-key-overview.png
index d150b8fb1b..d1fbe8f606 100644
Binary files a/docs/de/user/profile/assets/api-key-overview.png and b/docs/de/user/profile/assets/api-key-overview.png differ
diff --git a/docs/de/user/profile/index.md b/docs/de/user/profile/index.md
index 6e399bfa13..29018292d1 100644
--- a/docs/de/user/profile/index.md
+++ b/docs/de/user/profile/index.md
@@ -13,7 +13,7 @@ eingegeben werden. Danach muss das neue Passwort zweimal eingegeben werden.
## Ö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.
## API Schlüssel
diff --git a/docs/de/user/repo/assets/repository-branch-detailView.png b/docs/de/user/repo/assets/repository-branch-detailView.png
index d671846fef..f9727d9d4f 100644
Binary files a/docs/de/user/repo/assets/repository-branch-detailView.png and b/docs/de/user/repo/assets/repository-branch-detailView.png differ
diff --git a/docs/de/user/repo/assets/repository-branches-overview.png b/docs/de/user/repo/assets/repository-branches-overview.png
index 39dcf5e424..c839e6b57c 100644
Binary files a/docs/de/user/repo/assets/repository-branches-overview.png and b/docs/de/user/repo/assets/repository-branches-overview.png differ
diff --git a/docs/de/user/repo/assets/repository-overview.png b/docs/de/user/repo/assets/repository-overview.png
index 59cf55e4d2..2454f625a3 100644
Binary files a/docs/de/user/repo/assets/repository-overview.png and b/docs/de/user/repo/assets/repository-overview.png differ
diff --git a/docs/de/user/repo/branches.md b/docs/de/user/repo/branches.md
index 17afe92924..47ac022067 100644
--- a/docs/de/user/repo/branches.md
+++ b/docs/de/user/repo/branches.md
@@ -3,9 +3,11 @@ title: Repository
subtitle: Branches
---
### Ü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.
Über den "Branch erstellen"-Button gelangt man zum Formular, um neue Branches anzulegen.
diff --git a/docs/de/user/user/assets/user-information.png b/docs/de/user/user/assets/user-information.png
index f5573c10e8..b79a4076c7 100644
Binary files a/docs/de/user/user/assets/user-information.png and b/docs/de/user/user/assets/user-information.png differ
diff --git a/docs/de/user/user/assets/user-settings-general.png b/docs/de/user/user/assets/user-settings-general.png
index 9fe75a3a89..9f63014b71 100644
Binary files a/docs/de/user/user/assets/user-settings-general.png and b/docs/de/user/user/assets/user-settings-general.png differ
diff --git a/docs/de/user/user/assets/user-settings-publickeys.png b/docs/de/user/user/assets/user-settings-publickeys.png
index 9761a3b044..b40d52e2a8 100644
Binary files a/docs/de/user/user/assets/user-settings-publickeys.png and b/docs/de/user/user/assets/user-settings-publickeys.png differ
diff --git a/docs/en/user/profile/assets/api-key-overview.png b/docs/en/user/profile/assets/api-key-overview.png
index d150b8fb1b..d052188103 100644
Binary files a/docs/en/user/profile/assets/api-key-overview.png and b/docs/en/user/profile/assets/api-key-overview.png differ
diff --git a/docs/en/user/profile/index.md b/docs/en/user/profile/index.md
index 50746c2f56..5895b89700 100644
--- a/docs/en/user/profile/index.md
+++ b/docs/en/user/profile/index.md
@@ -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
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.
## API keys
diff --git a/docs/en/user/repo/assets/repository-branch-detailView.png b/docs/en/user/repo/assets/repository-branch-detailView.png
index 615ba696c1..28f768a6be 100644
Binary files a/docs/en/user/repo/assets/repository-branch-detailView.png and b/docs/en/user/repo/assets/repository-branch-detailView.png differ
diff --git a/docs/en/user/repo/assets/repository-branches-overview.png b/docs/en/user/repo/assets/repository-branches-overview.png
index e63ebab775..08a2ab19df 100644
Binary files a/docs/en/user/repo/assets/repository-branches-overview.png and b/docs/en/user/repo/assets/repository-branches-overview.png differ
diff --git a/docs/en/user/repo/assets/repository-overview.png b/docs/en/user/repo/assets/repository-overview.png
index 59cf55e4d2..7e6bfcbab7 100644
Binary files a/docs/en/user/repo/assets/repository-overview.png and b/docs/en/user/repo/assets/repository-overview.png differ
diff --git a/docs/en/user/repo/branches.md b/docs/en/user/repo/branches.md
index 370165710b..7a9d318786 100644
--- a/docs/en/user/repo/branches.md
+++ b/docs/en/user/repo/branches.md
@@ -4,6 +4,8 @@ subtitle: Branches
---
### Overview
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.
All branches except the default branch of the repository can be deleted by clicking on the trash bin icon.
diff --git a/docs/en/user/user/assets/user-information.png b/docs/en/user/user/assets/user-information.png
index f5573c10e8..666f05f7f8 100644
Binary files a/docs/en/user/user/assets/user-information.png and b/docs/en/user/user/assets/user-information.png differ
diff --git a/docs/en/user/user/assets/user-settings-general.png b/docs/en/user/user/assets/user-settings-general.png
index 4312fa0e8e..10d3688e47 100644
Binary files a/docs/en/user/user/assets/user-settings-general.png and b/docs/en/user/user/assets/user-settings-general.png differ
diff --git a/docs/en/user/user/assets/user-settings-publickeys.png b/docs/en/user/user/assets/user-settings-publickeys.png
index b2eae78e8b..286f46d8f2 100644
Binary files a/docs/en/user/user/assets/user-settings-publickeys.png and b/docs/en/user/user/assets/user-settings-publickeys.png differ
diff --git a/lerna.json b/lerna.json
index 210af935bc..a4385558c7 100644
--- a/lerna.json
+++ b/lerna.json
@@ -5,5 +5,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
- "version": "2.10.0-SNAPSHOT"
+ "version": "2.11.0-SNAPSHOT"
}
diff --git a/pom.xml b/pom.xml
index b8192de3a8..534bbceff0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
sonia.scmscmpom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
The easiest way to share your Git, Mercurial
and Subversion repositories.
@@ -464,7 +464,7 @@
org.assertjassertj-core
- 3.17.2
+ 3.18.0test
@@ -506,7 +506,7 @@
- jakarta.xml.bind
+ jakarta.xml.bindjakarta.xml.bind-api${jaxb.version}
@@ -580,7 +580,7 @@
sonia.scm.mavensmp-maven-plugin
- 1.3.0
+ 1.4.0
@@ -903,7 +903,7 @@
- 3.5.15
+ 3.6.02.15.7.0
@@ -919,13 +919,13 @@
4.2.32.3.36.1.6.Final
- 1.66
+ 1.671.6.2
- 9.4.34.v20201102
+ 9.4.35.v202011209.4.34.v20201102
@@ -937,7 +937,7 @@
1.10.1-scm2
- 26.0-jre
+ 30.0-jre12.16.1
diff --git a/scm-annotation-processor/pom.xml b/scm-annotation-processor/pom.xml
index 62087852cd..87b63417d9 100644
--- a/scm-annotation-processor/pom.xml
+++ b/scm-annotation-processor/pom.xml
@@ -31,12 +31,12 @@
sonia.scmscm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTsonia.scmscm-annotation-processor
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-annotation-processor
@@ -46,7 +46,7 @@
sonia.scmscm-annotations
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-annotations/pom.xml b/scm-annotations/pom.xml
index 6e70ada1ab..f83dac689c 100644
--- a/scm-annotations/pom.xml
+++ b/scm-annotations/pom.xml
@@ -31,11 +31,11 @@
sonia.scmscm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-annotations
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-annotations
diff --git a/scm-core/pom.xml b/scm-core/pom.xml
index 85a539c471..e54ff52f01 100644
--- a/scm-core/pom.xml
+++ b/scm-core/pom.xml
@@ -31,11 +31,11 @@
scmsonia.scm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-core
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-core
@@ -54,7 +54,7 @@
sonia.scmscm-annotations
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
@@ -227,7 +227,7 @@
sonia.scmscm-annotation-processor
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTprovided
@@ -250,6 +250,12 @@
test
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
diff --git a/scm-core/src/main/java/sonia/scm/TransactionId.java b/scm-core/src/main/java/sonia/scm/TransactionId.java
new file mode 100644
index 0000000000..68169ba7d4
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/TransactionId.java
@@ -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 get() {
+ return Optional.ofNullable(MDC.get(KEY));
+ }
+
+ /**
+ * Removes a bound transaction id from the current thread.
+ */
+ public static void clear() {
+ MDC.remove(KEY);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/Branch.java b/scm-core/src/main/java/sonia/scm/repository/Branch.java
index bdcbc66a82..acfdd6a884 100644
--- a/scm-core/src/main/java/sonia/scm/repository/Branch.java
+++ b/scm-core/src/main/java/sonia/scm/repository/Branch.java
@@ -24,8 +24,6 @@
package sonia.scm.repository;
-//~--- non-JDK imports --------------------------------------------------------
-
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
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.XmlRootElement;
import java.io.Serializable;
+import java.util.Optional;
import java.util.regex.Pattern;
-//~--- JDK imports ------------------------------------------------------------
-
/**
* Represents a branch in a repository.
*
@@ -46,73 +43,100 @@ import java.util.regex.Pattern;
*/
@XmlRootElement(name = "branch")
@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 = VALID_CHARACTERS_AT_START_AND_END + "/.";
public static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
public static final Pattern VALID_BRANCH_NAME_PATTERN = Pattern.compile(VALID_BRANCH_NAMES);
- /** Field description */
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.
* This constructor should only be called from JAXB.
- *
*/
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 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 null
*/
- Branch(String name, String revision, boolean defaultBranch)
- {
+ Branch(String name, String revision, boolean defaultBranch, Long lastCommitDate) {
this.name = name;
this.revision = revision;
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) {
- 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) {
- 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
public boolean isValid() {
return VALID_BRANCH_NAME_PATTERN.matcher(name).matches();
}
- /**
- * {@inheritDoc}
- *
- *
- * @param obj
- *
- * @return
- */
@Override
- public boolean equals(Object obj)
- {
- if (obj == null)
- {
+ public boolean equals(Object obj) {
+ if (obj == null) {
return false;
}
- if (getClass() != obj.getClass())
- {
+ if (getClass() != obj.getClass()) {
return false;
}
@@ -120,48 +144,31 @@ public final class Branch implements Serializable, Validateable
return Objects.equal(name, other.name)
&& Objects.equal(revision, other.revision)
- && Objects.equal(defaultBranch, other.defaultBranch);
+ && Objects.equal(defaultBranch, other.defaultBranch)
+ && Objects.equal(lastCommitDate, other.lastCommitDate);
}
- /**
- * {@inheritDoc}
- *
- *
- * @return
- */
@Override
- public int hashCode()
- {
+ public int hashCode() {
return Objects.hashCode(name, revision);
}
- /**
- * {@inheritDoc}
- *
- *
- * @return
- */
@Override
- public String toString()
- {
- //J-
+ public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("revision", revision)
+ .add("defaultBranch", defaultBranch)
+ .add("lastCommitDate", lastCommitDate)
.toString();
- //J+
}
- //~--- get methods ----------------------------------------------------------
-
/**
* Returns the name of the branch
*
- *
* @return name of the branch
*/
- public String getName()
- {
+ public String getName() {
return name;
}
@@ -170,22 +177,27 @@ public final class Branch implements Serializable, Validateable
*
* @return latest revision of branch
*/
- public String getRevision()
- {
+ public String getRevision() {
return revision;
}
+ /**
+ * Flag whether this branch is configured as the default branch.
+ */
public boolean isDefaultBranch() {
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 getLastCommitDate() {
+ return Optional.ofNullable(lastCommitDate);
+ }
- /** name of the branch */
- private String name;
-
- /** Field description */
- private String revision;
-
- private boolean defaultBranch;
+ public boolean isStale() {
+ return stale;
+ }
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchXDaysOlderThanDefaultStaleComputer.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchXDaysOlderThanDefaultStaleComputer.java
new file mode 100644
index 0000000000..15de6663ec
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchXDaysOlderThanDefaultStaleComputer.java
@@ -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();
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java
index 5ba45b00a0..af784f287b 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.api;
import com.google.common.base.Objects;
@@ -165,7 +165,7 @@ public final class BranchesCommandBuilder
private Branches getBranchesFromCommand()
throws IOException
{
- return new Branches(branchesCommand.getBranches());
+ return new Branches(branchesCommand.getBranchesWithStaleFlags(new BranchXDaysOlderThanDefaultStaleComputer()));
}
//~--- inner classes --------------------------------------------------------
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchStaleComputer.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchStaleComputer.java
new file mode 100644
index 0000000000..c6324fa1cf
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchStaleComputer.java
@@ -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;
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchesCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchesCommand.java
index ae1468e768..9fa28a49aa 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/BranchesCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchesCommand.java
@@ -21,33 +21,52 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
-package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
+package sonia.scm.repository.spi;
import sonia.scm.repository.Branch;
import java.io.IOException;
import java.util.List;
-
-//~--- JDK imports ------------------------------------------------------------
+import java.util.Optional;
/**
- *
* @author Sebastian Sdorra
* @since 1.18
*/
-public interface BranchesCommand
-{
+public interface BranchesCommand {
- /**
- * Method description
- *
- *
- * @return
- *
- * @throws IOException
- */
List getBranches() throws IOException;
+
+ default List getBranchesWithStaleFlags(BranchStaleComputer computer) throws IOException {
+ List branches = getBranches();
+ new StaleProcessor(computer, branches).process();
+ return branches;
+ }
+
+ final class StaleProcessor {
+
+ private final BranchStaleComputer computer;
+ private final List branches;
+
+ private StaleProcessor(BranchStaleComputer computer, List branches) {
+ this.computer = computer;
+ this.branches = branches;
+ }
+
+ private void process() {
+ Optional 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)));
+ }
+ }
}
diff --git a/scm-core/src/test/java/sonia/scm/TransactionIdTest.java b/scm-core/src/test/java/sonia/scm/TransactionIdTest.java
new file mode 100644
index 0000000000..053f2ce86c
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/TransactionIdTest.java
@@ -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();
+ }
+
+}
diff --git a/scm-core/src/test/java/sonia/scm/repository/api/BranchXDaysOlderThanDefaultStaleComputerTest.java b/scm-core/src/test/java/sonia/scm/repository/api/BranchXDaysOlderThanDefaultStaleComputerTest.java
new file mode 100644
index 0000000000..12fb269162
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/repository/api/BranchXDaysOlderThanDefaultStaleComputerTest.java
@@ -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;
+ }
+}
diff --git a/scm-core/src/test/java/sonia/scm/repository/spi/BranchesCommandTest.java b/scm-core/src/test/java/sonia/scm/repository/spi/BranchesCommandTest.java
new file mode 100644
index 0000000000..4a0f9326c0
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/repository/spi/BranchesCommandTest.java
@@ -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 branches = asList(
+ Branch.normalBranch("arthur", "42", staleTime),
+ Branch.normalBranch("marvin", "42", staleTime),
+ Branch.defaultBranch("hog", "42", now.toEpochMilli()),
+ Branch.normalBranch("trillian", "42", activeTime)
+ );
+
+ List branchesWithStaleFlags = new BranchesCommand() {
+ @Override
+ public List getBranches() {
+ return branches;
+ }
+ }.getBranchesWithStaleFlags(new BranchXDaysOlderThanDefaultStaleComputer());
+
+ Assertions.assertThat(branchesWithStaleFlags)
+ .extracting("stale")
+ .containsExactly(true, true, false, false);
+ }
+}
diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml
index 0f43eafa51..e9001f190e 100644
--- a/scm-dao-xml/pom.xml
+++ b/scm-dao-xml/pom.xml
@@ -31,11 +31,11 @@
sonia.scmscm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-dao-xml
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-dao-xml
@@ -50,7 +50,7 @@
sonia.scmscm-core
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
@@ -58,7 +58,7 @@
sonia.scmscm-test
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTtest
diff --git a/scm-it/pom.xml b/scm-it/pom.xml
index 1817845989..acc6971500 100644
--- a/scm-it/pom.xml
+++ b/scm-it/pom.xml
@@ -31,40 +31,40 @@
sonia.scmscm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTsonia.scmscm-itwar
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-itsonia.scmscm-core
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTsonia.scmscm-test
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTsonia.scm.pluginsscm-git-plugin
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTtestsonia.scm.pluginsscm-git-plugin
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTteststest
@@ -72,14 +72,14 @@
sonia.scm.pluginsscm-hg-plugin
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTtestsonia.scm.pluginsscm-hg-plugin
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTteststest
@@ -87,14 +87,14 @@
sonia.scm.pluginsscm-svn-plugin
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTtestsonia.scm.pluginsscm-svn-plugin
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTteststest
diff --git a/scm-packaging/deb/pom.xml b/scm-packaging/deb/pom.xml
index 569e1d9e73..232841d07d 100644
--- a/scm-packaging/deb/pom.xml
+++ b/scm-packaging/deb/pom.xml
@@ -32,12 +32,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTdebdeb
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTPackaging for Debian/Ubuntudeb
@@ -46,7 +46,7 @@
sonia.scmscm-server
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-packaging/docker/Dockerfile b/scm-packaging/docker/Dockerfile
index 9c0451a38e..a69c584d65 100644
--- a/scm-packaging/docker/Dockerfile
+++ b/scm-packaging/docker/Dockerfile
@@ -41,4 +41,9 @@ VOLUME ["${SCM_HOME}", "${CACHE_DIR}"]
EXPOSE 8080
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" ]
diff --git a/scm-packaging/docker/pom.xml b/scm-packaging/docker/pom.xml
index b12f198695..adba6c8271 100644
--- a/scm-packaging/docker/pom.xml
+++ b/scm-packaging/docker/pom.xml
@@ -32,12 +32,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTdockerpom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-packaging/helm/pom.xml b/scm-packaging/helm/pom.xml
index 2a642dee27..6d53125f2d 100644
--- a/scm-packaging/helm/pom.xml
+++ b/scm-packaging/helm/pom.xml
@@ -32,12 +32,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOThelmhelm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT3.2.1
diff --git a/scm-packaging/pom.xml b/scm-packaging/pom.xml
index faf3e5d404..592139e03f 100644
--- a/scm-packaging/pom.xml
+++ b/scm-packaging/pom.xml
@@ -31,13 +31,13 @@
sonia.scmscm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTsonia.scm.packagingscm-packagingpom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTpackages.scm-manager.org
diff --git a/scm-packaging/release-yaml/pom.xml b/scm-packaging/release-yaml/pom.xml
index f2bb1e07d2..a8b6dc0a94 100644
--- a/scm-packaging/release-yaml/pom.xml
+++ b/scm-packaging/release-yaml/pom.xml
@@ -32,12 +32,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTrelease-yamlpom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-packaging/rpm/pom.xml b/scm-packaging/rpm/pom.xml
index 98b4dacad7..072a6effed 100644
--- a/scm-packaging/rpm/pom.xml
+++ b/scm-packaging/rpm/pom.xml
@@ -32,12 +32,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTrpmrpm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTPackaging for RedHat/Centos/Fedorarpm
@@ -52,7 +52,7 @@
sonia.scmscm-server
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-packaging/unix/pom.xml b/scm-packaging/unix/pom.xml
index 41063ca51d..a7d09c302c 100644
--- a/scm-packaging/unix/pom.xml
+++ b/scm-packaging/unix/pom.xml
@@ -31,12 +31,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTunixpom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-packaging/windows/pom.xml b/scm-packaging/windows/pom.xml
index 617f6a8b34..2fb9a1f595 100644
--- a/scm-packaging/windows/pom.xml
+++ b/scm-packaging/windows/pom.xml
@@ -32,12 +32,12 @@
sonia.scm.packagingscm-packaging
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTwindowspom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOT
diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml
index 33b1061c3f..2c61f07a89 100644
--- a/scm-plugins/pom.xml
+++ b/scm-plugins/pom.xml
@@ -31,13 +31,13 @@
sonia.scmscm
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTsonia.scm.pluginsscm-pluginspom
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-plugins
@@ -60,7 +60,7 @@
sonia.scmscm-core
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTprovided
@@ -69,7 +69,7 @@
sonia.scmscm-annotation-processor
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTprovided
@@ -99,7 +99,7 @@
sonia.scmscm-test
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTtest
diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json
index a0ca9cf3ac..4f4087a3f6 100644
--- a/scm-plugins/scm-git-plugin/package.json
+++ b/scm-plugins/scm-git-plugin/package.json
@@ -1,7 +1,7 @@
{
"name": "@scm-manager/scm-git-plugin",
"private": true,
- "version": "2.10.0-SNAPSHOT",
+ "version": "2.11.0-SNAPSHOT",
"license": "MIT",
"main": "./src/main/js/index.ts",
"scripts": {
@@ -20,6 +20,6 @@
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {
- "@scm-manager/ui-plugins": "^2.10.0-SNAPSHOT"
+ "@scm-manager/ui-plugins": "^2.11.0-SNAPSHOT"
}
}
diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml
index 23c7e50527..b5c374758d 100644
--- a/scm-plugins/scm-git-plugin/pom.xml
+++ b/scm-plugins/scm-git-plugin/pom.xml
@@ -31,7 +31,7 @@
scm-pluginssonia.scm.plugins
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-git-plugin
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java
index 893de773da..99ac469166 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java
@@ -39,7 +39,7 @@ import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
@NoArgsConstructor
@Getter
@Setter
-public class GitConfigDto extends HalRepresentation {
+public class GitConfigDto extends HalRepresentation implements UpdateGitConfigDto {
private boolean disabled = false;
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java
index 71d97f27be..e366a35abb 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java
@@ -27,7 +27,9 @@ package sonia.scm.api.v2.resources;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
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.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.config.ConfigurationPermissions;
@@ -116,7 +118,23 @@ public class GitConfigResource {
@PUT
@Path("")
@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 = "401", description = "not authenticated / invalid credentials")
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:git\" privilege")
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java
index 92a495669b..46eb671d5c 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
@@ -36,7 +36,7 @@ import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@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;
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java
index 3aa348d13a..559dfddae8 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java
@@ -21,13 +21,16 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.api.v2.resources;
import io.swagger.v3.oas.annotations.Operation;
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.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryConfig;
@@ -106,7 +109,22 @@ public class GitRepositoryConfigResource {
@PUT
@Path("/")
@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(
responseCode = "204",
description = "update success"
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateGitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateGitConfigDto.java
new file mode 100644
index 0000000000..66bdcd7278
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateGitConfigDto.java
@@ -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();
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateGitRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateGitRepositoryConfigDto.java
new file mode 100644
index 0000000000..0da843e1af
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateGitRepositoryConfigDto.java
@@ -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();
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java
index d026affd8b..683e5d0068 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java
@@ -24,14 +24,14 @@
package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
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.LoggerFactory;
import sonia.scm.repository.Branch;
@@ -44,12 +44,9 @@ import java.util.List;
import java.util.Optional;
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 {
private static final Logger LOG = LoggerFactory.getLogger(GitBranchesCommand.class);
@@ -60,23 +57,22 @@ public class GitBranchesCommand extends AbstractGitCommand implements BranchesCo
super(context);
}
- //~--- get methods ----------------------------------------------------------
-
@Override
public List getBranches() throws IOException {
Git git = createGit();
String defaultBranchName = determineDefaultBranchName(git);
- try {
+ Repository repository = git.getRepository();
+ try (RevWalk refWalk = new RevWalk(repository)) {
return git
.branchList()
.call()
.stream()
- .map(ref -> createBranchObject(defaultBranchName, ref))
+ .map(ref -> createBranchObject(repository, refWalk, defaultBranchName, ref))
.collect(Collectors.toList());
} 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
- private Branch createBranchObject(String defaultBranchName, Ref ref) {
+ private Branch createBranchObject(Repository repository, RevWalk refWalk, String defaultBranchName, Ref ref) {
String branchName = GitUtil.getBranch(ref);
if (branchName == null) {
LOG.warn("could not determine branch name for branch name {} at revision {}", ref.getName(), ref.getObjectId());
return null;
} else {
+ Long lastCommitDate = getCommitDate(repository, refWalk, branchName, ref);
if (branchName.equals(defaultBranchName)) {
- return Branch.defaultBranch(branchName, GitUtil.getId(ref.getObjectId()));
+ return Branch.defaultBranch(branchName, GitUtil.getId(ref.getObjectId()), lastCommitDate);
} 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) {
String defaultBranchName = context.getConfig().getDefaultBranch();
if (Strings.isNullOrEmpty(defaultBranchName)) {
diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java
index 967226012a..fddf386d92 100644
--- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java
@@ -24,117 +24,26 @@
package sonia.scm.repository.spi;
-import org.eclipse.jgit.api.Git;
-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 org.junit.Test;
import sonia.scm.repository.Branch;
-import sonia.scm.repository.GitRepositoryConfig;
import java.io.IOException;
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.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-@ExtendWith(MockitoExtension.class)
-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 getRepositoryHeadRef(Git git) {
- return of(master);
- }
- };
- when(git.branchList()).thenReturn(listBranchCommand);
- }
+public class GitBranchesCommandTest extends AbstractGitCommandTestBase {
@Test
- void shouldCreateEmptyListWithoutBranches() throws IOException, GitAPIException {
- when(listBranchCommand.call()).thenReturn(emptyList());
+ public void shouldReadBranches() throws IOException {
+ GitBranchesCommand branchesCommand = new GitBranchesCommand(createContext());
List branches = branchesCommand.getBranches();
- assertThat(branches).isEmpty();
- }
-
- @Test
- void shouldMapNormalBranch() throws IOException, GitAPIException {
- Ref branch = createRef("branch", "1337");
- when(listBranchCommand.call()).thenReturn(asList(branch));
-
- List 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 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 branches = branchesCommand.getBranches();
-
- assertThat(branches).containsExactlyInAnyOrder(
- Branch.normalBranch("branch", "1337"),
- Branch.defaultBranch("master", "0000")
+ assertThat(branches).contains(
+ Branch.defaultBranch("master", "fcd0ef1831e4002ac43ea539f4094334c79ea9ec", 1339428655000L),
+ Branch.normalBranch("mergeable", "91b99de908fcd04772798a31c308a64aea1a5523", 1541586052000L),
+ Branch.normalBranch("rename", "383b954b27e052db6880d57f1c860dc208795247", 1589203061000L)
);
}
-
- 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;
- }
}
diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json
index 107dde3e2e..8aaa4d0b34 100644
--- a/scm-plugins/scm-hg-plugin/package.json
+++ b/scm-plugins/scm-hg-plugin/package.json
@@ -1,7 +1,7 @@
{
"name": "@scm-manager/scm-hg-plugin",
"private": true,
- "version": "2.10.0-SNAPSHOT",
+ "version": "2.11.0-SNAPSHOT",
"license": "MIT",
"main": "./src/main/js/index.ts",
"scripts": {
@@ -19,6 +19,6 @@
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {
- "@scm-manager/ui-plugins": "^2.10.0-SNAPSHOT"
+ "@scm-manager/ui-plugins": "^2.11.0-SNAPSHOT"
}
}
diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml
index a6f125a6f9..6f1563d5a4 100644
--- a/scm-plugins/scm-hg-plugin/pom.xml
+++ b/scm-plugins/scm-hg-plugin/pom.xml
@@ -31,7 +31,7 @@
sonia.scm.pluginsscm-plugins
- 2.10.0-SNAPSHOT
+ 2.11.0-SNAPSHOTscm-hg-plugin
@@ -44,15 +44,29 @@
com.aragost.javahgjavahg
- 0.15-scm1
+ 0.16com.google.guavaguava
+
+ org.slf4j
+ slf4j-simple
+
+
+ org.slf4j
+ slf4j-nop
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java
index 2d8c385128..70eb367d3c 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java
@@ -27,7 +27,9 @@ package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import io.swagger.v3.oas.annotations.Operation;
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.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.repository.HgConfig;
@@ -83,7 +85,22 @@ public class HgConfigAutoConfigurationResource {
@PUT
@Path("")
@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(
responseCode = "204",
description = "update success"
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java
index b5039cbb44..3ddb33052f 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
@@ -30,10 +30,12 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
-@NoArgsConstructor
@Getter
@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;
@@ -44,7 +46,6 @@ public class HgConfigDto extends HalRepresentation {
private boolean useOptimizedBytecode;
private boolean showRevisionInId;
private boolean enableHttpPostArgs;
- private boolean disableHookSSLValidation;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java
index cb2ac532ab..0c715ce8c3 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java
@@ -27,7 +27,9 @@ package sonia.scm.api.v2.resources;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
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.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.config.ConfigurationPermissions;
@@ -121,7 +123,23 @@ public class HgConfigResource {
@PUT
@Path("")
@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(
responseCode = "204",
description = "update success"
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java
new file mode 100644
index 0000000000..7106c7c85a
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java
@@ -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();
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java
deleted file mode 100644
index a74fdf9b21..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java
+++ /dev/null
@@ -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 createEnvironment(String revision, String path)
- {
- Map 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(), args);
- }
-
- /**
- * Method description
- *
- *
- * @param extraEnv
- * @param args
- *
- * @return
- *
- * @throws IOException
- */
- protected Process createHgProcess(Map 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 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 getResultFromScript(Class resultType, HgPythonScript script) throws IOException {
- return getResultFromScript(resultType, script,
- new HashMap());
- }
-
- @SuppressWarnings("unchecked")
- protected T getResultFromScript(Class resultType,
- HgPythonScript script, Map 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 extraEnv, String cmd,
- String... args)
- throws IOException
- {
- HgConfig config = handler.getConfig();
- List cmdList = new ArrayList();
-
- cmdList.add(cmd);
-
- if (Util.isNotEmpty(args))
- {
- cmdList.addAll(Arrays.asList(args));
- }
-
- if (logger.isDebugEnabled())
- {
- StringBuilder msg = new StringBuilder("create process for [");
- Iterator 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 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 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;
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java
new file mode 100644
index 0000000000..1888f0aeae
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java
@@ -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 read(Repository repository) {
+ ImmutableMap.Builder env = ImmutableMap.builder();
+ read(env, repository);
+ return env.build();
+ }
+
+ @Override
+ public Map write(Repository repository) {
+ ImmutableMap.Builder env = ImmutableMap.builder();
+ read(env, repository);
+ write(env);
+ return env.build();
+ }
+
+ private void read(ImmutableMap.Builder 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 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;
+ }
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java
index daf3414a63..3189239dbd 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
@@ -36,20 +36,10 @@ import javax.xml.bind.annotation.XmlTransient;
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "config")
-public class HgConfig extends RepositoryConfig
-{
+public class HgConfig extends RepositoryConfig {
public static final String PERMISSION = "hg";
- /**
- * Constructs ...
- *
- */
- public HgConfig() {}
-
- //~--- get methods ----------------------------------------------------------
-
-
@Override
@XmlTransient // Only for permission checks, don't serialize to XML
public String getId() {
@@ -123,10 +113,6 @@ public class HgConfig extends RepositoryConfig
return useOptimizedBytecode;
}
- public boolean isDisableHookSSLValidation() {
- return disableHookSSLValidation;
- }
-
public boolean isEnableHttpPostArgs() {
return enableHttpPostArgs;
}
@@ -216,10 +202,6 @@ public class HgConfig extends RepositoryConfig
this.useOptimizedBytecode = useOptimizedBytecode;
}
- public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
- this.disableHookSSLValidation = disableHookSSLValidation;
- }
-
//~--- fields ---------------------------------------------------------------
/** Field description */
@@ -242,9 +224,4 @@ public class HgConfig extends RepositoryConfig
private boolean enableHttpPostArgs = false;
- /**
- * disable validation of ssl certificates for mercurial hook
- * @see Issue 959
- */
- private boolean disableHookSSLValidation = false;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java
deleted file mode 100644
index 2791fff3c0..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java
+++ /dev/null
@@ -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;
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java
deleted file mode 100644
index 6b09416db2..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java
+++ /dev/null
@@ -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
-{
-
- /**
- * the LOG for HgContextProvider
- */
- private static final Logger LOG =
- LoggerFactory.getLogger(HgContextProvider.class);
-
- //~--- get methods ----------------------------------------------------------
-
- private Provider requestStoreProvider;
-
- @Inject
- public HgContextProvider(Provider 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;
- }
- }
- }
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
deleted file mode 100644
index a997bb2069..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
+++ /dev/null
@@ -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 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 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 environment, AccessToken accessToken) {
- environment.put(SCM_XSRF, accessToken.getCustom(Xsrf.TOKEN_KEY).orElse("-"));
- }
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java
similarity index 59%
rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java
rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java
index 3fa04bd1e6..3738d92d88 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java
@@ -21,28 +21,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
-import com.google.inject.servlet.RequestScoped;
+import com.google.inject.ImplementedBy;
-/**
- * Holds an instance of {@link HgContext} in the request scope.
- *
- *
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.
- *
- *
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.
- */
-@RequestScoped
-public class HgContextRequestStore {
-
- private final HgContext context = new HgContext();
-
- public HgContext get() {
- return context;
- }
+import java.util.Map;
+@ImplementedBy(DefaultHgEnvironmentBuilder.class)
+public interface HgEnvironmentBuilder {
+ Map read(Repository repository);
+ Map write(Repository repository);
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
deleted file mode 100644
index 3d333015ee..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
+++ /dev/null
@@ -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 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 httpServletRequestProvider;
-
- private final AccessTokenBuilderFactory accessTokenBuilderFactory;
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java
index a7c5ef6069..0e4b05b101 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java
@@ -38,80 +38,30 @@ import java.io.File;
*/
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");
-
- /** Field description */
+ private static final String BASE_DIRECTORY = "lib".concat(File.separator).concat("python");
private static final String BASE_RESOURCE = "/sonia/scm/python/";
- //~--- constructors ---------------------------------------------------------
+ private final String name;
- /**
- * Constructs ...
- *
- *
- * @param name
- */
- private HgPythonScript(String name)
- {
+ HgPythonScript(String name) {
this.name = name;
}
- //~--- get methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param context
- *
- * @return
- */
- public static File getScriptDirectory(SCMContextProvider context)
- {
+ public static File getScriptDirectory(SCMContextProvider context) {
return new File(context.getBaseDirectory(), BASE_DIRECTORY);
}
- /**
- * Method description
- *
- *
- * @param context
- *
- * @return
- */
- public File getFile(SCMContextProvider context)
- {
+ public File getFile(SCMContextProvider context) {
return new File(getScriptDirectory(context), name);
}
- /**
- * Method description
- *
- *
- * @return
- */
- public String getName()
- {
+ public String getName() {
return name;
}
- /**
- * Method description
- *
- *
- * @return
- */
- public String getResourcePath()
- {
+ public String getResourcePath() {
return BASE_RESOURCE.concat(name);
}
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private String name;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java
new file mode 100644
index 0000000000..5b17ad8bd4
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java
@@ -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 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 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 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;
+ }
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java
index bdc36eb54b..14cd54866d 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java
@@ -27,11 +27,9 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.ConfigurationException;
import sonia.scm.SCMContextProvider;
import sonia.scm.autoconfig.AutoConfigurator;
import sonia.scm.installer.HgInstaller;
@@ -43,14 +41,14 @@ import sonia.scm.io.INISection;
import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
+import sonia.scm.repository.spi.HgVersionCommand;
import sonia.scm.repository.spi.HgWorkingCopyFactory;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.IOUtil;
import sonia.scm.util.SystemUtil;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -63,14 +61,15 @@ import java.util.Optional;
public class HgRepositoryHandler
extends AbstractSimpleRepositoryHandler {
- 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 TYPE_DISPLAYNAME = "Mercurial";
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,
HgRepositoryServiceProvider.COMMANDS,
- HgRepositoryServiceProvider.FEATURES);
+ HgRepositoryServiceProvider.FEATURES
+ );
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_KEY_REPOSITORY_ID = "repositoryid";
- private final Provider hgContextProvider;
-
private final HgWorkingCopyFactory workingCopyFactory;
- private final JAXBContext jaxbContext;
-
@Inject
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
- Provider hgContextProvider,
RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
super(storeFactory, repositoryLocationResolver, pluginLoader);
- this.hgContextProvider = hgContextProvider;
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) {
@@ -107,8 +92,7 @@ public class HgRepositoryHandler
try {
if (logger.isDebugEnabled()) {
- logger.debug("installing mercurial with {}",
- installer.getClass().getName());
+ logger.debug("installing mercurial with {}", installer.getClass().getName());
}
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
public ImportHandler getImportHandler() {
return new HgImportHandler(this);
@@ -176,28 +150,14 @@ public class HgRepositoryHandler
@Override
public String getVersionInformation() {
- String version = getStringFromResource(RESOURCE_VERSION,
- DEFAULT_VERSION_INFORMATION);
+ return getVersionInformation(new HgVersionCommand(getConfig()));
+ }
- try {
- HgVersion hgVersion = new HgVersionHandler(this, hgContextProvider.get(),
- baseDirectory).getVersion();
-
- if (hgVersion != null) {
- if (logger.isDebugEnabled()) {
- logger.debug("mercurial/python informations: {}", hgVersion);
- }
-
- 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;
+ String getVersionInformation(HgVersionCommand command) {
+ String version = getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION);
+ HgVersion hgVersion = command.get();
+ logger.debug("mercurial/python informations: {}", hgVersion);
+ return MessageFormat.format(version, hgVersion.getPython(), hgVersion.getMercurial());
}
@Override
@@ -253,28 +213,24 @@ public class HgRepositoryHandler
logger.debug("write python script {}", script.getName());
}
- InputStream content = null;
- OutputStream output = null;
-
- try {
- content = HgRepositoryHandler.class.getResourceAsStream(
- script.getResourcePath());
- output = new FileOutputStream(script.getFile(context));
+ try (InputStream content = input(script); OutputStream output = output(context, script)) {
IOUtil.copy(content, output);
} catch (IOException 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() {
return workingCopyFactory;
}
- public JAXBContext getJaxbContext() {
- return jaxbContext;
- }
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java
index 36bd6abfa3..a517c2fe08 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java
@@ -24,10 +24,8 @@
package sonia.scm.repository;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
+import lombok.AllArgsConstructor;
+import lombok.Data;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -37,13 +35,14 @@ import javax.xml.bind.annotation.XmlRootElement;
*
* @author Sebastian Sdorra
*/
+@Data
+@AllArgsConstructor
@XmlRootElement(name = "version")
@XmlAccessorType(XmlAccessType.FIELD)
-@EqualsAndHashCode
-@Getter
-@Setter
-@ToString
public class HgVersion {
+
+ public static final String UNKNOWN = "x.y.z (unknown)";
+
private String mercurial;
private String python;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java
index ea1671d7ab..0cd78ac932 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java
@@ -21,75 +21,31 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.api;
//~--- JDK imports ------------------------------------------------------------
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
import java.io.Serializable;
/**
*
* @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;
- //~--- constant enums -------------------------------------------------------
-
- /**
- * 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 Severity severity;
private String message;
- /** Field description */
- private Severity severity;
+ public enum Severity { NOTE, ERROR }
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java
new file mode 100644
index 0000000000..c189299d72
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java
@@ -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 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 messages;
+ private boolean abort;
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java
similarity index 52%
rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java
rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java
index 259996e2ed..04f88189ce 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java
@@ -21,60 +21,37 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
-package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
+package sonia.scm.repository.hooks;
-import sonia.scm.repository.AbstractHgHandler;
-import sonia.scm.repository.HgContext;
+import sonia.scm.NotFoundException;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
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;
-/**
- *
- * @author Sebastian Sdorra
- */
-public class AbstractHgCommand extends AbstractHgHandler
-{
-
- /**
- * Constructs ...
- *
- *
- * @param handler
- * @param context
- * @param repository
- * @param repositoryDirectory
- */
- protected AbstractHgCommand(HgRepositoryHandler handler, HgContext context,
- Repository repository, File repositoryDirectory)
- {
- super(handler, context, repository, repositoryDirectory);
+ @Inject
+ public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) {
+ this.repositoryManager = repositoryManager;
+ this.repositoryHandler = repositoryHandler;
+ this.repositoryFactory = repositoryFactory;
}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param revision
- * @param path
- *
- * @param request
- *
- * @return
- */
- protected Map createEnvironment(FileBaseCommandRequest request)
- {
- return createEnvironment(request.getRevision(), request.getPath());
+ HgHookContextProvider create(String repositoryId, String node) {
+ Repository repository = repositoryManager.get(repositoryId);
+ if (repository == null) {
+ throw new NotFoundException(Repository.class, repositoryId);
+ }
+ return new HgHookContextProvider(repositoryHandler, repositoryFactory, repository, node);
}
+
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookEnvironment.java
new file mode 100644
index 0000000000..a1d205a9d5
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookEnvironment.java
@@ -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 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;
+ }
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java
new file mode 100644
index 0000000000..f2f7e7ccdd
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java
@@ -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 {
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java
new file mode 100644
index 0000000000..4eaf205bdd
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java
@@ -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);
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java
new file mode 100644
index 0000000000..2eed226a09
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java
@@ -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)
+ );
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java
new file mode 100644
index 0000000000..90a15c6fb4
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java
@@ -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();
+ }
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/Sockets.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/Sockets.java
new file mode 100644
index 0000000000..4a0389a33c
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/Sockets.java
@@ -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 receive(InputStream in, Class 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);
+ }
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java
index 22cccbc8ae..a2285bad11 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java
@@ -27,7 +27,6 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.aragost.javahg.Changeset;
-import com.google.common.base.Function;
import com.google.common.collect.Lists;
import sonia.scm.repository.Branch;
@@ -63,14 +62,8 @@ public class HgBranchesCommand extends AbstractCommand
List hgBranches =
com.aragost.javahg.commands.BranchesCommand.on(open()).execute();
- List branches = Lists.transform(hgBranches,
- new Function()
- {
-
- @Override
- public Branch apply(com.aragost.javahg.Branch hgBranch)
- {
+ return Lists.transform(hgBranches,
+ hgBranch -> {
String node = null;
Changeset changeset = hgBranch.getBranchTip();
@@ -79,14 +72,12 @@ public class HgBranchesCommand extends AbstractCommand
node = changeset.getNode();
}
+ long lastCommitDate = changeset.getTimestamp().getDate().getTime();
if (DEFAULT_BRANCH_NAME.equals(hgBranch.getName())) {
- return Branch.defaultBranch(hgBranch.getName(), node);
+ return Branch.defaultBranch(hgBranch.getName(), node, lastCommitDate);
} else {
- return Branch.normalBranch(hgBranch.getName(), node);
+ return Branch.normalBranch(hgBranch.getName(), node, lastCommitDate);
}
- }
- });
-
- return branches;
+ });
}
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java
index 39181d3d67..5169d3c389 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java
@@ -27,18 +27,12 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.aragost.javahg.Repository;
-import com.google.common.base.Strings;
import sonia.scm.repository.HgConfig;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryProvider;
-import sonia.scm.web.HgUtil;
import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.function.BiConsumer;
//~--- JDK imports ------------------------------------------------------------
@@ -46,105 +40,32 @@ import java.util.function.BiConsumer;
*
* @author Sebastian Sdorra
*/
-public class HgCommandContext implements Closeable, RepositoryProvider
-{
+public class HgCommandContext implements Closeable, RepositoryProvider {
- /** Field description */
- private static final String PROPERTY_ENCODING = "hg.encoding";
+ private final HgRepositoryHandler handler;
+ private final HgRepositoryFactory factory;
+ private final sonia.scm.repository.Repository scmRepository;
- //~--- constructors ---------------------------------------------------------
+ private Repository repository;
- /**
- * 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;
+ public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
this.handler = handler;
- this.directory = directory;
- this.scmRepository = repository;
- this.encoding = repository.getProperty(PROPERTY_ENCODING);
- this.pending = pending;
-
- if (Strings.isNullOrEmpty(encoding))
- {
- encoding = handler.getConfig().getEncoding();
- }
+ this.factory = factory;
+ this.scmRepository = scmRepository;
}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @throws IOException
- */
- @Override
- public void close() throws IOException
- {
- if (repository != null)
- {
- repository.close();
+ public Repository open() {
+ if (repository == null) {
+ repository = factory.openForRead(scmRepository);
}
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public Repository open()
- {
- if (repository == null)
- {
- repository = HgUtil.open(handler, hookManager, directory, encoding, pending);
- }
-
return repository;
}
- public Repository openWithSpecialEnvironment(BiConsumer> prepareEnvironment)
- {
- return HgUtil.open(handler, directory, encoding,
- pending, environment -> prepareEnvironment.accept(scmRepository, environment));
+ public Repository openForWrite() {
+ return factory.openForWrite(scmRepository);
}
- //~--- get methods ----------------------------------------------------------
- /**
- * Method description
- *
- *
- * @return
- */
public HgConfig getConfig()
{
return handler.getConfig();
@@ -159,25 +80,12 @@ public class HgCommandContext implements Closeable, RepositoryProvider
return getScmRepository();
}
- //~--- fields ---------------------------------------------------------------
- /** Field description */
- private File directory;
+ @Override
+ 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;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java
index 10fe43bcf7..98bddd8c7c 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java
@@ -21,85 +21,56 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
-package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
+package sonia.scm.repository.spi;
import com.aragost.javahg.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
-import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
import sonia.scm.web.HgUtil;
-import java.io.File;
-
-//~--- JDK imports ------------------------------------------------------------
-
/**
*
* @author Sebastian Sdorra
*/
-public class HgHookChangesetProvider implements HookChangesetProvider
-{
+public class HgHookChangesetProvider implements HookChangesetProvider {
- /**
- * the logger for HgHookChangesetProvider
- */
- private static final Logger logger =
- LoggerFactory.getLogger(HgHookChangesetProvider.class);
+ private static final Logger LOG = 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,
- File repositoryDirectory, HgHookManager hookManager, String startRev,
- RepositoryHookType type)
- {
+ private HookChangesetResponse response;
+
+ public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
this.handler = handler;
- this.repositoryDirectory = repositoryDirectory;
- this.hookManager = hookManager;
+ this.factory = factory;
+ this.scmRepository = scmRepository;
this.startRev = startRev;
- this.type = type;
}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param request
- *
- * @return
- */
@Override
- public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request)
- {
- if (response == null)
- {
+ public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
+ if (response == null) {
Repository repository = null;
- try
- {
- repository = open();
+ try {
+ repository = factory.openForRead(scmRepository);
- HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository,
- handler.getConfig());
+ HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig());
response = new HookChangesetResponse(
- cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute());
- }
- catch (Exception ex)
- {
- logger.error("could not retrieve changesets", ex);
- }
- finally
- {
- if (repository != null)
- {
+ cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()
+ );
+ } catch (Exception ex) {
+ LOG.error("could not retrieve changesets", ex);
+ } finally {
+ if (repository != null) {
repository.close();
}
}
@@ -108,39 +79,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider
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;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java
index 1f72d56e24..0282729baa 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java
@@ -21,14 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
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.HgHookMessageProvider;
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.HookTagProvider;
-import java.io.File;
import java.util.EnumSet;
import java.util.Set;
@@ -45,55 +44,40 @@ import java.util.Set;
/**
* Mercurial implementation of {@link HookContextProvider}.
- *
+ *
* @author Sebastian Sdorra
*/
-public class HgHookContextProvider extends HookContextProvider
-{
+public class HgHookContextProvider extends HookContextProvider {
- private static final Set SUPPORTED_FEATURES =
- EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER,
- HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
+ private static final Set SUPPORTED_FEATURES = EnumSet.of(
+ HookFeature.CHANGESET_PROVIDER,
+ HookFeature.MESSAGE_PROVIDER,
+ HookFeature.BRANCH_PROVIDER,
+ HookFeature.TAG_PROVIDER
+ );
- //~--- constructors ---------------------------------------------------------
+ private final HgHookChangesetProvider hookChangesetProvider;
+ private HgHookMessageProvider hgMessageProvider;
+ private HgHookBranchProvider hookBranchProvider;
+ private HgHookTagProvider hookTagProvider;
- /**
- * Constructs a new instance.
- *
- * @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);
+ public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) {
+ this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev);
}
- //~--- get methods ----------------------------------------------------------
-
@Override
- public HookBranchProvider getBranchProvider()
- {
- if (hookBranchProvider == null)
- {
+ public HookBranchProvider getBranchProvider() {
+ if (hookBranchProvider == null) {
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
}
-
return hookBranchProvider;
}
@Override
- public HookTagProvider getTagProvider()
- {
- if (hookTagProvider == null)
- {
+ public HookTagProvider getTagProvider() {
+ if (hookTagProvider == null) {
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
}
-
return hookTagProvider;
}
@@ -102,14 +86,11 @@ public class HgHookContextProvider extends HookContextProvider
{
return hookChangesetProvider;
}
-
- public HgHookMessageProvider getHgMessageProvider()
- {
- if (hgMessageProvider == null)
- {
+
+ public HgHookMessageProvider getHgMessageProvider() {
+ if (hgMessageProvider == null) {
hgMessageProvider = new HgHookMessageProvider();
}
-
return hgMessageProvider;
}
@@ -119,21 +100,9 @@ public class HgHookContextProvider extends HookContextProvider
return SUPPORTED_FEATURES;
}
- //~--- methods --------------------------------------------------------------
-
@Override
protected HookMessageProvider createMessageProvider()
{
return getHgMessageProvider();
}
-
- //~--- fields ---------------------------------------------------------------
-
- private final HgHookChangesetProvider hookChangesetProvider;
-
- private HgHookMessageProvider hgMessageProvider;
-
- private HgHookBranchProvider hookBranchProvider;
-
- private HgHookTagProvider hookTagProvider;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
index 302bea6d4f..aaea12eeac 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
@@ -31,6 +31,8 @@ import com.aragost.javahg.commands.ExecutionException;
import com.aragost.javahg.commands.PullCommand;
import com.aragost.javahg.commands.RemoveCommand;
import com.aragost.javahg.commands.StatusCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import sonia.scm.NoChangesMadeException;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.work.WorkingCopy;
@@ -41,11 +43,13 @@ import java.nio.file.Path;
import java.util.List;
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 {
+ private static final Logger LOG = LoggerFactory.getLogger(HgModifyCommand.class);
static final Pattern HG_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\](?: Error:)? (.*)");
- private HgCommandContext context;
+ private final HgCommandContext context;
private final HgWorkingCopyFactory workingCopyFactory;
public HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
@@ -55,7 +59,6 @@ public class HgModifyCommand implements ModifyCommand {
@Override
public String execute(ModifyCommandRequest request) {
-
try (WorkingCopy workingCopy = workingCopyFactory.createWorkingCopy(context, request.getBranch())) {
Repository workingRepository = workingCopy.getWorkingRepository();
request.getRequests().forEach(
@@ -100,12 +103,21 @@ public class HgModifyCommand implements ModifyCommand {
}
}
);
+
if (StatusCommand.on(workingRepository).lines().isEmpty()) {
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 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) {
throwInternalRepositoryException("could not execute command on repository", e);
return null;
@@ -113,6 +125,7 @@ public class HgModifyCommand implements ModifyCommand {
}
private List pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy workingCopy) {
+ LOG.trace("pull changes from working copy");
try {
com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
workingCopyFactory.configure(pullCommand);
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
index b7eece2b9f..df40fdf4ae 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
@@ -26,13 +26,12 @@ package sonia.scm.repository.spi;
import com.google.common.io.Closeables;
import sonia.scm.repository.Feature;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.CommandNotSupportedException;
-import java.io.File;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
@@ -41,11 +40,8 @@ import java.util.Set;
*
* @author Sebastian Sdorra
*/
-public class HgRepositoryServiceProvider extends RepositoryServiceProvider
-{
+public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
- /** Field description */
- //J-
public static final Set COMMANDS = EnumSet.of(
Command.BLAME,
Command.BROWSE,
@@ -61,25 +57,19 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
Command.PULL,
Command.MODIFY
);
- //J+
- /** Field description */
- public static final Set FEATURES =
- EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
+ public static final Set FEATURES = EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
- //~--- constructors ---------------------------------------------------------
+ private final HgRepositoryHandler handler;
+ private final HgCommandContext context;
- HgRepositoryServiceProvider(HgRepositoryHandler handler,
- HgHookManager hookManager, Repository repository)
- {
+ HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) {
this.handler = handler;
- this.repositoryDirectory = handler.getDirectory(repository.getId());
- this.context = new HgCommandContext(hookManager, handler, repository,
- repositoryDirectory);
+ this.context = new HgCommandContext(handler, factory, repository);
}
-
//~--- methods --------------------------------------------------------------
+
/**
* Method description
*
@@ -91,9 +81,9 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
{
Closeables.close(context, true);
}
-
//~--- get methods ----------------------------------------------------------
+
/**
* Method description
*
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java
index 4af799f30a..87a5d6d6fb 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java
@@ -21,12 +21,12 @@
* 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.inject.Inject;
import sonia.scm.plugin.Extension;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -35,18 +35,15 @@ import sonia.scm.repository.Repository;
* @author Sebastian Sdorra
*/
@Extension
-public class HgRepositoryServiceResolver implements RepositoryServiceResolver
-{
+public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
private final HgRepositoryHandler handler;
- private final HgHookManager hookManager;
+ private final HgRepositoryFactory factory;
@Inject
- public HgRepositoryServiceResolver(HgRepositoryHandler handler,
- HgHookManager hookManager)
- {
+ public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) {
this.handler = handler;
- this.hookManager = hookManager;
+ this.factory = factory;
}
@Override
@@ -54,7 +51,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
HgRepositoryServiceProvider provider = null;
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
- provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
+ provider = new HgRepositoryServiceProvider(handler, factory, repository);
}
return provider;
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java
new file mode 100644
index 0000000000..2d4e44f5a7
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java
@@ -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 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 command) throws IOException;
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java
index 4e4c4cf80c..8c01c369ff 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java
@@ -36,27 +36,21 @@ import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.work.SimpleWorkingCopyFactory;
import sonia.scm.repository.work.WorkingCopyPool;
import sonia.scm.util.IOUtil;
-import sonia.scm.web.HgRepositoryEnvironmentBuilder;
import javax.inject.Inject;
-import javax.inject.Provider;
import java.io.File;
import java.io.IOException;
-import java.util.Map;
-import java.util.function.BiConsumer;
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory implements HgWorkingCopyFactory {
- private final Provider hgRepositoryEnvironmentBuilder;
-
@Inject
- public SimpleHgWorkingCopyFactory(Provider hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) {
+ public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) {
super(workdirProvider);
- this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
}
+
@Override
public ParentAndClone initialize(HgCommandContext context, File target, String initialBranch) {
- Repository centralRepository = openCentral(context);
+ Repository centralRepository = context.openForWrite();
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
if (initialBranch != null) {
cloneCommand.updaterev(initialBranch);
@@ -76,7 +70,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
- Repository centralRepository = openCentral(context);
+ Repository centralRepository = context.openForWrite();
try {
BaseRepository clone = Repository.open(target);
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
@@ -89,12 +83,6 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory> repositoryMapBiConsumer =
- (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment);
- return context.openWithSpecialEnvironment(repositoryMapBiConsumer);
- }
-
private void delete(File directory, String unknownFile) throws IOException {
IOUtil.delete(new File(directory, unknownFile));
}
@@ -111,7 +99,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory 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
*
@@ -192,7 +158,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
executor.setExceptionHandler(exceptionHandler);
executor.setStatusCodeHandler(exceptionHandler);
executor.setContentLengthWorkaround(true);
- hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
+
+ EnvList env = executor.getEnvironment();
+ environmentBuilder.write(repository).forEach(env::set);
String interpreter = getInterpreter();
@@ -248,5 +216,5 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
/** Field description */
private final RepositoryRequestListenerUtil requestListenerUtil;
- private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder;
+ private final HgEnvironmentBuilder environmentBuilder;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java
deleted file mode 100644
index 5b0609cc72..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java
+++ /dev/null
@@ -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 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 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 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 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 contextProvider;
-
- /** Field description */
- private final HgRepositoryHandler handler;
-
- /** Field description */
- private final HookEventFacade hookEventFacade;
-
- /** Field description */
- private final HgHookManager hookManager;
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java
deleted file mode 100644
index 6b626b5c33..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java
+++ /dev/null
@@ -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 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
- );
- }
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java
index 5271751faa..64a27990a8 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java
@@ -34,9 +34,6 @@ import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper;
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
import sonia.scm.installer.HgPackageReader;
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.SimpleHgWorkingCopyFactory;
@@ -45,26 +42,10 @@ import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
* @author Sebastian Sdorra
*/
@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
- protected void configureServlets()
- {
- bind(HgContext.class).toProvider(HgContextProvider.class);
- bind(HgHookManager.class);
+ protected void configureServlets() {
bind(HgPackageReader.class);
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(HgConfigInstallationsToDtoMapper.class);
- // bind servlets
- serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
-
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
}
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java
index 234580ed6f..3ba797aff2 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java
@@ -21,189 +21,48 @@
* 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.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.repository.HgConfig;
-import sonia.scm.repository.HgEnvironment;
-import sonia.scm.repository.HgHookManager;
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;
-//~--- JDK imports ------------------------------------------------------------
-
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
*/
-public final class HgUtil
-{
+public final class HgUtil {
- /** Field description */
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() {}
- //~--- methods --------------------------------------------------------------
-
- /**
- * 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