Browse Source

Upgrade to bitbucket server 4.0.1 (WIP)

pull/40/head
Adrian Gonzalez 9 years ago
parent
commit
fc5316370c
  1. 4
      README.md
  2. 134
      pom.xml
  3. 12
      src/main/java/com/englishtown/bitbucket/hook/DefaultPasswordEncryptor.java
  4. 4
      src/main/java/com/englishtown/bitbucket/hook/DefaultSettingsReflectionHelper.java
  5. 66
      src/main/java/com/englishtown/bitbucket/hook/MirrorRepositoryHook.java
  6. 2
      src/main/java/com/englishtown/bitbucket/hook/PasswordEncryptor.java
  7. 8
      src/main/java/com/englishtown/bitbucket/hook/PasswordHandler.java
  8. 4
      src/main/java/com/englishtown/bitbucket/hook/SettingsReflectionHelper.java
  9. 20
      src/main/resources/atlassian-plugin.xml
  10. 2
      src/main/resources/bitbucket-hook-mirror.properties
  11. 16
      src/main/resources/static/mirror-repository-hook.soy
  12. 2
      src/test/java/com/englishtown/bitbucket/hook/DefaultPasswordEncryptorTest.java
  13. 107
      src/test/java/com/englishtown/bitbucket/hook/DefaultSettingsReflectionHelperTest.java
  14. 108
      src/test/java/com/englishtown/bitbucket/hook/MirrorRepositoryHookTest.java
  15. 4
      src/test/java/com/englishtown/bitbucket/hook/PasswordHandlerTest.java
  16. 36
      src/test/java/com/englishtown/stash/hook/DefaultSettingsReflectionHelperTest.java

4
README.md

@ -1,8 +1,8 @@
[![Build Status](https://travis-ci.org/englishtown/stash-hook-mirror.png)](https://travis-ci.org/englishtown/stash-hook-mirror) [![Build Status](https://travis-ci.org/englishtown/stash-hook-mirror.png)](https://travis-ci.org/englishtown/stash-hook-mirror)
#Stash Repository Hook for Mirroring #Bitbucket Server Repository Hook for Mirroring
The following is a plugin for Atlassian Stash to provide repository mirroring to a remote repository. The following is a plugin for Atlassian Bitbucket Server to provide repository mirroring to a remote repository.
* `atlas-run` -- installs this plugin into the product and starts it on localhost * `atlas-run` -- installs this plugin into the product and starts it on localhost

134
pom.xml

@ -6,15 +6,15 @@
<groupId>com.englishtown</groupId> <groupId>com.englishtown</groupId>
<artifactId>stash-hook-mirror</artifactId> <artifactId>stash-hook-mirror</artifactId>
<version>1.10.0-SNAPSHOT</version> <version>2.0.0-SNAPSHOT</version>
<organization> <organization>
<name>Englishtown</name> <name>Englishtown</name>
<url>http://www.englishtown.com/</url> <url>http://www.englishtown.com/</url>
</organization> </organization>
<name>Repository Mirror Plugin for Stash</name> <name>Repository Mirror Plugin for Bitbucket Server</name>
<description>A Stash repository hook for mirroring to one or more remote repositories.</description> <description>A Bitbucket Server repository hook for mirroring to one or more remote repositories.</description>
<url>https://github.com/englishtown/stash-hook-mirror</url> <url>https://github.com/englishtown/stash-hook-mirror</url>
<inceptionYear>2013</inceptionYear> <inceptionYear>2013</inceptionYear>
<licenses> <licenses>
@ -27,26 +27,20 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<stash.version>2.6.0</stash.version>
<stash.data.version>2.6.0</stash.data.version> <bitbucket.version>4.0.1</bitbucket.version>
<amps.version>4.2.20</amps.version> <bitbucket.data.version>${bitbucket.version}</bitbucket.data.version>
<plugin.testrunner.version>1.1.7</plugin.testrunner.version> <atlassian-sal-api.version>3.0.5</atlassian-sal-api.version>
<amps.version>6.1.0</amps.version>
<junit.version>4.10</junit.version>
<common-lang.version>2.6</common-lang.version>
<plugin.compiler.version>3.1</plugin.compiler.version>
<mockito.version>1.8.5</mockito.version>
<gson.version>2.2.2-atlassian-1</gson.version>
<jsr311.version>1.1.1</jsr311.version>
<slf4j.version>1.7.5</slf4j.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.atlassian.stash</groupId> <groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>stash-parent</artifactId> <artifactId>bitbucket-parent</artifactId>
<version>${stash.version}</version> <version>${bitbucket.version}</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -55,116 +49,64 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.atlassian.stash</groupId> <groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>stash-scm-git-api</artifactId> <artifactId>bitbucket-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.atlassian.stash</groupId> <groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>stash-api</artifactId> <artifactId>bitbucket-spi</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.atlassian.stash</groupId> <groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>stash-spi</artifactId> <artifactId>bitbucket-git-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.atlassian.stash</groupId> <groupId>com.atlassian.sal</groupId>
<artifactId>stash-page-objects</artifactId> <artifactId>sal-api</artifactId>
<scope>provided</scope> <version>${atlassian-sal-api.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${common-lang.version}</version>
</dependency>
<!-- WIRED TEST RUNNER DEPENDENCIES -->
<dependency>
<groupId>com.atlassian.stash</groupId>
<artifactId>stash-service-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.plugins</groupId>
<artifactId>atlassian-plugins-osgi-testrunner</artifactId>
<version>${plugin.testrunner.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>${jsr311.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId> <artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<!-- Workaround for AMPS-1112 -->
<plugin> <plugin>
<groupId>com.atlassian.maven.plugins</groupId> <groupId>com.atlassian.maven.plugins</groupId>
<artifactId>maven-amps-plugin</artifactId> <artifactId>bitbucket-maven-plugin</artifactId>
<version>${amps.version}</version>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>com.atlassian.maven.plugins</groupId>
<artifactId>maven-stash-plugin</artifactId>
<version>${amps.version}</version> <version>${amps.version}</version>
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<products> <products>
<product> <product>
<id>stash</id> <id>bitbucket</id>
<instanceId>stash</instanceId> <instanceId>bitbucket</instanceId>
<version>${stash.version}</version> <version>${bitbucket.version}</version>
<dataVersion>${stash.data.version}</dataVersion> <dataVersion>${bitbucket.data.version}</dataVersion>
</product> </product>
</products> </products>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>${plugin.compiler.version}</version> <version>3.2</version>
<configuration> <configuration>
<source>1.6</source> <source>1.8</source>
<target>1.6</target> <target>1.8</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
@ -173,12 +115,7 @@
<repositories> <repositories>
<repository> <repository>
<id>atlassian-public</id> <id>atlassian-public</id>
<url>https://m2proxy.atlassian.com/repository/public</url> <url>https://maven.atlassian.com/content/groups/public</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<releases> <releases>
<enabled>true</enabled> <enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy> <checksumPolicy>warn</checksumPolicy>
@ -189,14 +126,11 @@
<pluginRepositories> <pluginRepositories>
<pluginRepository> <pluginRepository>
<id>atlassian-public</id> <id>atlassian-public</id>
<url>https://m2proxy.atlassian.com/repository/public</url> <url>https://maven.atlassian.com/content/groups/public</url>
<releases> <releases>
<enabled>true</enabled> <enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy> <checksumPolicy>warn</checksumPolicy>
</releases> </releases>
<snapshots>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>

12
src/main/java/com/englishtown/stash/hook/DefaultPasswordEncryptor.java → src/main/java/com/englishtown/bitbucket/hook/DefaultPasswordEncryptor.java

@ -1,13 +1,13 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.sal.api.pluginsettings.PluginSettings; import com.atlassian.sal.api.pluginsettings.PluginSettings;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.*; import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/** /**
* Service to encrypt/decrypt git user passwords * Service to encrypt/decrypt git user passwords
@ -29,11 +29,11 @@ public class DefaultPasswordEncryptor implements PasswordEncryptor {
if (value == null || value.toString().isEmpty()) { if (value == null || value.toString().isEmpty()) {
KeyGenerator gen = KeyGenerator.getInstance("AES"); KeyGenerator gen = KeyGenerator.getInstance("AES");
secretKey = gen.generateKey(); secretKey = gen.generateKey();
keyBase64 = Base64.encodeBase64String(secretKey.getEncoded()); keyBase64 = Base64.getEncoder().encodeToString(secretKey.getEncoded());
pluginSettings.put(SETTINGS_CRYPTO_KEY, keyBase64); pluginSettings.put(SETTINGS_CRYPTO_KEY, keyBase64);
} else { } else {
keyBase64 = value.toString(); keyBase64 = value.toString();
byte[] data = Base64.decodeBase64(keyBase64); byte[] data = Base64.getDecoder().decode(keyBase64);
secretKey = new SecretKeySpec(data, 0, data.length, "AES"); secretKey = new SecretKeySpec(data, 0, data.length, "AES");
} }
@ -92,7 +92,7 @@ public class DefaultPasswordEncryptor implements PasswordEncryptor {
} }
try { try {
byte[] encryptedData = runCipher(password.getBytes("UTF-8"), true); byte[] encryptedData = runCipher(password.getBytes("UTF-8"), true);
return ENCRYPTED_PREFIX + Base64.encodeBase64String(encryptedData); return ENCRYPTED_PREFIX + Base64.getEncoder().encodeToString(encryptedData);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -104,7 +104,7 @@ public class DefaultPasswordEncryptor implements PasswordEncryptor {
return password; return password;
} }
try { try {
byte[] encryptedData = Base64.decodeBase64(password.substring(ENCRYPTED_PREFIX.length())); byte[] encryptedData = Base64.getDecoder().decode(password.substring(ENCRYPTED_PREFIX.length()));
byte[] clearData = runCipher(encryptedData, false); byte[] clearData = runCipher(encryptedData, false);
return new String(clearData, "UTF-8"); return new String(clearData, "UTF-8");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {

4
src/main/java/com/englishtown/stash/hook/DefaultSettingsReflectionHelper.java → src/main/java/com/englishtown/bitbucket/hook/DefaultSettingsReflectionHelper.java

@ -1,6 +1,6 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.stash.setting.Settings; import com.atlassian.bitbucket.setting.Settings;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Map; import java.util.Map;

66
src/main/java/com/englishtown/stash/hook/MirrorRepositoryHook.java → src/main/java/com/englishtown/bitbucket/hook/MirrorRepositoryHook.java

@ -1,20 +1,21 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.bitbucket.hook.repository.AsyncPostReceiveRepositoryHook;
import com.atlassian.bitbucket.hook.repository.RepositoryHookContext;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.scm.CommandExitHandler;
import com.atlassian.bitbucket.scm.DefaultCommandExitHandler;
import com.atlassian.bitbucket.scm.ScmCommandBuilder;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.setting.RepositorySettingsValidator;
import com.atlassian.bitbucket.setting.Settings;
import com.atlassian.bitbucket.setting.SettingsValidationErrors;
import com.atlassian.sal.api.pluginsettings.PluginSettings; import com.atlassian.sal.api.pluginsettings.PluginSettings;
import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory; import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import com.atlassian.stash.hook.repository.AsyncPostReceiveRepositoryHook;
import com.atlassian.stash.hook.repository.RepositoryHookContext;
import com.atlassian.stash.i18n.I18nService;
import com.atlassian.stash.repository.RefChange;
import com.atlassian.stash.repository.Repository;
import com.atlassian.stash.repository.RepositoryMetadataService;
import com.atlassian.stash.scm.CommandExitHandler;
import com.atlassian.stash.scm.DefaultCommandExitHandler;
import com.atlassian.stash.scm.git.GitScm;
import com.atlassian.stash.scm.git.GitScmCommandBuilder;
import com.atlassian.stash.setting.RepositorySettingsValidator;
import com.atlassian.stash.setting.Settings;
import com.atlassian.stash.setting.SettingsValidationErrors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,7 +24,6 @@ import java.lang.reflect.Method;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -42,33 +42,33 @@ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, Rep
static final String SETTING_PASSWORD = "password"; static final String SETTING_PASSWORD = "password";
static final int MAX_ATTEMPTS = 5; static final int MAX_ATTEMPTS = 5;
private final GitScm gitScm; private final ScmService scmService;
private final I18nService i18nService; private final I18nService i18nService;
private final ScheduledExecutorService executor; private final ScheduledExecutorService executor;
private final PasswordEncryptor passwordEncryptor; private final PasswordEncryptor passwordEncryptor;
private final SettingsReflectionHelper settingsReflectionHelper; private final SettingsReflectionHelper settingsReflectionHelper;
private final RepositoryMetadataService repositoryMetadataService; private final RepositoryService repositoryService;
private static final Logger logger = LoggerFactory.getLogger(MirrorRepositoryHook.class); private static final Logger logger = LoggerFactory.getLogger(MirrorRepositoryHook.class);
public MirrorRepositoryHook( public MirrorRepositoryHook(
GitScm gitScm, ScmService scmService,
I18nService i18nService, I18nService i18nService,
ScheduledExecutorService executor, ScheduledExecutorService executor,
PasswordEncryptor passwordEncryptor, PasswordEncryptor passwordEncryptor,
SettingsReflectionHelper settingsReflectionHelper, SettingsReflectionHelper settingsReflectionHelper,
PluginSettingsFactory pluginSettingsFactory, PluginSettingsFactory pluginSettingsFactory,
RepositoryMetadataService repositoryMetadataService RepositoryService repositoryService
) { ) {
logger.debug("MirrorRepositoryHook: init started"); logger.debug("MirrorRepositoryHook: init started");
// Set fields // Set fields
this.gitScm = gitScm; this.scmService = scmService;
this.i18nService = i18nService; this.i18nService = i18nService;
this.executor = executor; this.executor = executor;
this.passwordEncryptor = passwordEncryptor; this.passwordEncryptor = passwordEncryptor;
this.settingsReflectionHelper = settingsReflectionHelper; this.settingsReflectionHelper = settingsReflectionHelper;
this.repositoryMetadataService = repositoryMetadataService; this.repositoryService = repositoryService;
// Init password encryptor // Init password encryptor
PluginSettings pluginSettings = pluginSettingsFactory.createSettingsForKey(PLUGIN_SETTINGS_KEY); PluginSettings pluginSettings = pluginSettingsFactory.createSettingsForKey(PLUGIN_SETTINGS_KEY);
@ -78,11 +78,11 @@ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, Rep
} }
/** /**
* Calls the remote stash instance(s) to push the latest changes * Calls the remote bitbucket instance(s) to push the latest changes
* <p/> * <p>
* Callback method that is called just after a push is completed (or a pull request accepted). * Callback method that is called just after a push is completed (or a pull request accepted).
* This hook executes <i>after</i> the processing of a push and will not block the user client. * This hook executes <i>after</i> the processing of a push and will not block the user client.
* <p/> * <p>
* Despite being asynchronous, the user who initiated this change is still available from * Despite being asynchronous, the user who initiated this change is still available from
* *
* @param context the context which the hook is being run with * @param context the context which the hook is being run with
@ -104,7 +104,7 @@ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, Rep
} }
void runMirrorCommand(MirrorSettings settings, final Repository repository) { void runMirrorCommand(MirrorSettings settings, final Repository repository) {
if (repositoryMetadataService.isEmpty(repository)) { if (repositoryService.isEmpty(repository)) {
return; return;
} }
@ -112,14 +112,19 @@ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, Rep
final String password = passwordEncryptor.decrypt(settings.password); final String password = passwordEncryptor.decrypt(settings.password);
final String authenticatedUrl = getAuthenticatedUrl(settings.mirrorRepoUrl, settings.username, password); final String authenticatedUrl = getAuthenticatedUrl(settings.mirrorRepoUrl, settings.username, password);
executor.submit(new Callable<Void>() { executor.submit(new Runnable() {
int attempts = 0; int attempts = 0;
@Override @Override
public Void call() throws Exception { public void run() {
try { try {
GitScmCommandBuilder builder = gitScm.getCommandBuilderFactory().builder(repository); ScmCommandBuilder obj = scmService.createBuilder(repository);
if (!(obj instanceof GitScmCommandBuilder)) {
logger.warn("Repository " + repository.getName() + " is not a git repo, cannot mirror");
return;
}
GitScmCommandBuilder builder = (GitScmCommandBuilder) obj;
PasswordHandler passwordHandler = getPasswordHandler(builder, password); PasswordHandler passwordHandler = getPasswordHandler(builder, password);
// Call push command with the prune flag and refspecs for heads and tags // Call push command with the prune flag and refspecs for heads and tags
@ -149,7 +154,6 @@ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, Rep
} }
} }
return null;
} }
}); });
@ -215,7 +219,7 @@ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, Rep
protected List<MirrorSettings> getMirrorSettings(Settings settings) { protected List<MirrorSettings> getMirrorSettings(Settings settings) {
List<MirrorSettings> results = new ArrayList<MirrorSettings>(); List<MirrorSettings> results = new ArrayList<>();
Map<String, Object> allSettings = settings.asMap(); Map<String, Object> allSettings = settings.asMap();
int count = 0; int count = 0;

2
src/main/java/com/englishtown/stash/hook/PasswordEncryptor.java → src/main/java/com/englishtown/bitbucket/hook/PasswordEncryptor.java

@ -1,4 +1,4 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.sal.api.pluginsettings.PluginSettings; import com.atlassian.sal.api.pluginsettings.PluginSettings;

8
src/main/java/com/englishtown/stash/hook/PasswordHandler.java → src/main/java/com/englishtown/bitbucket/hook/PasswordHandler.java

@ -1,8 +1,8 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.stash.scm.CommandErrorHandler; import com.atlassian.bitbucket.scm.CommandErrorHandler;
import com.atlassian.stash.scm.CommandExitHandler; import com.atlassian.bitbucket.scm.CommandExitHandler;
import com.atlassian.stash.scm.CommandOutputHandler; import com.atlassian.bitbucket.scm.CommandOutputHandler;
import com.atlassian.utils.process.StringOutputHandler; import com.atlassian.utils.process.StringOutputHandler;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;

4
src/main/java/com/englishtown/stash/hook/SettingsReflectionHelper.java → src/main/java/com/englishtown/bitbucket/hook/SettingsReflectionHelper.java

@ -1,6 +1,6 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.stash.setting.Settings; import com.atlassian.bitbucket.setting.Settings;
import java.util.Map; import java.util.Map;

20
src/main/resources/atlassian-plugin.xml

@ -8,31 +8,27 @@
</plugin-info> </plugin-info>
<!-- Components that are injected --> <!-- Components that are injected -->
<component-import key="i18nService" interface="com.atlassian.stash.i18n.I18nService"/> <component-import key="i18nService" interface="com.atlassian.bitbucket.i18n.I18nService"/>
<component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/> <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/>
<component-import key="pluginSettingsFactory" interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory"/> <component-import key="pluginSettingsFactory" interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory"/>
<component-import key="scmService" interface="com.atlassian.bitbucket.scm.ScmService"/>
<component-import key="gitScm" interface="com.atlassian.stash.scm.git.GitScm"/> <component key="passwordEncryptor" class="com.englishtown.bitbucket.hook.DefaultPasswordEncryptor">
<component-import key="gitAgent" interface="com.atlassian.stash.scm.git.GitAgent"/> <interface>com.englishtown.bitbucket.hook.PasswordEncryptor</interface>
<component-import key="gitScmConfig" interface="com.atlassian.stash.scm.git.GitScmConfig"/>
<component-import key="gitCommandBuilderFactory" interface="com.atlassian.stash.scm.git.GitCommandBuilderFactory"/>
<component key="passwordEncryptor" class="com.englishtown.stash.hook.DefaultPasswordEncryptor">
<interface>com.englishtown.stash.hook.PasswordEncryptor</interface>
</component> </component>
<component key="settingsReflectionHelper" class="com.englishtown.stash.hook.DefaultSettingsReflectionHelper"> <component key="settingsReflectionHelper" class="com.englishtown.bitbucket.hook.DefaultSettingsReflectionHelper">
<interface>com.englishtown.stash.hook.SettingsReflectionHelper</interface> <interface>com.englishtown.bitbucket.hook.SettingsReflectionHelper</interface>
</component> </component>
<!-- add our i18n resource --> <!-- add our i18n resource -->
<resource type="i18n" name="i18n" location="stash-hook-mirror"/> <resource type="i18n" name="i18n" location="stash-hook-mirror"/>
<repository-hook name="Mirror Repository Hook" i18n-name-key="mirror-repository-hook.name" <repository-hook name="Mirror Repository Hook" i18n-name-key="mirror-repository-hook.name"
key="mirror-repository-hook" class="com.englishtown.stash.hook.MirrorRepositoryHook"> key="mirror-repository-hook" class="com.englishtown.bitbucket.hook.MirrorRepositoryHook">
<description key="mirror-repository-hook.description">Mirror Hook</description> <description key="mirror-repository-hook.description">Mirror Hook</description>
<icon>/icons/mirror-icon.png</icon> <icon>/icons/mirror-icon.png</icon>
<config-form name="Mirror Hook Config" key="mirror-repository-hook-config"> <config-form name="Mirror Hook Config" key="mirror-repository-hook-config">
<view>com.englishtown.stash.hook.mirrorrepositoryhook.view</view> <view>com.englishtown.bitbucket.hook.mirrorrepositoryhook.view</view>
<directory location="/static/"/> <directory location="/static/"/>
</config-form> </config-form>
</repository-hook> </repository-hook>

2
src/main/resources/stash-hook-mirror.properties → src/main/resources/bitbucket-hook-mirror.properties

@ -1,4 +1,4 @@
#put any key/value pairs here #put any key/value pairs here
my.plugin.name=stash-hook-mirror my.plugin.name=stash-hook-mirror
mirror-repository-hook.name=Mirror Hook mirror-repository-hook.name=Mirror Hook
mirror-repository-hook.description=Mirrors a stash repository to one or more remote repositories. mirror-repository-hook.description=Mirrors a bitbucket repository to one or more remote repositories.

16
src/main/resources/static/mirror-repository-hook.soy

@ -1,4 +1,4 @@
{namespace com.englishtown.stash.hook.mirrorrepositoryhook} {namespace com.englishtown.bitbucket.hook.mirrorrepositoryhook}
/** /**
* @param config * @param config
@ -29,7 +29,7 @@
// Call init method with subview callback // Call init method with subview callback
<script> <script>
require("et/hook/mirror").init(com.englishtown.stash.hook.mirrorrepositoryhook.subview, stash.buttons.button); require("et/hook/mirror").init(com.englishtown.bitbucket.hook.mirrorrepositoryhook.subview, stash.buttons.button);
</script> </script>
{/template} {/template}
@ -44,10 +44,10 @@
{param id: 'mirrorRepoUrl' + $index /} {param id: 'mirrorRepoUrl' + $index /}
{param value: $config['mirrorRepoUrl' + $index] /} {param value: $config['mirrorRepoUrl' + $index] /}
{param labelContent} {param labelContent}
{stash_i18n('com.englishtown.stash.hook.mirror.strings.mirrorRepoUrl.label', 'Mirror Repo URL')} {stash_i18n('com.englishtown.bitbucket.hook.mirror.strings.mirrorRepoUrl.label', 'Mirror Repo URL')}
{/param} {/param}
{param isRequired: true /} {param isRequired: true /}
{param descriptionText: stash_i18n('com.englishtown.stash.hook.mirror.strings.mirrorRepoUrl.description', {param descriptionText: stash_i18n('com.englishtown.bitbucket.hook.mirror.strings.mirrorRepoUrl.description',
'The GIT URL (ssh, git, http(s), file) to the remote mirrored repo') /} 'The GIT URL (ssh, git, http(s), file) to the remote mirrored repo') /}
{param extraClasses: 'long et-mirror-repo' /} {param extraClasses: 'long et-mirror-repo' /}
{param errorTexts: $errors ? $errors['mirrorRepoUrl' + $index] : null /} {param errorTexts: $errors ? $errors['mirrorRepoUrl' + $index] : null /}
@ -56,9 +56,9 @@
{param id: 'username' + $index /} {param id: 'username' + $index /}
{param value: $config['username' + $index] /} {param value: $config['username' + $index] /}
{param labelContent} {param labelContent}
{stash_i18n('com.englishtown.stash.hook.mirror.strings.username.label', 'Username')} {stash_i18n('com.englishtown.bitbucket.hook.mirror.strings.username.label', 'Username')}
{/param} {/param}
{param descriptionText: stash_i18n('com.englishtown.stash.hook.mirror.strings.username.description', {param descriptionText: stash_i18n('com.englishtown.bitbucket.hook.mirror.strings.username.description',
'The username to use for pushing to the mirror over http(s)') /} 'The username to use for pushing to the mirror over http(s)') /}
{param extraClasses: 'long' /} {param extraClasses: 'long' /}
{param errorTexts: $errors ? $errors['username' + $index] : null /} {param errorTexts: $errors ? $errors['username' + $index] : null /}
@ -67,9 +67,9 @@
{param id: 'password' + $index /} {param id: 'password' + $index /}
{param value: $config['password' + $index] /} {param value: $config['password' + $index] /}
{param labelContent} {param labelContent}
{stash_i18n('com.englishtown.stash.hook.mirror.strings.password.label', 'Password')} {stash_i18n('com.englishtown.bitbucket.hook.mirror.strings.password.label', 'Password')}
{/param} {/param}
{param descriptionText: stash_i18n('com.englishtown.stash.hook.mirror.strings.password.description', {param descriptionText: stash_i18n('com.englishtown.bitbucket.hook.mirror.strings.password.description',
'The password to use for pushing to the mirror over http(s)') /} 'The password to use for pushing to the mirror over http(s)') /}
{param extraClasses: 'long' /} {param extraClasses: 'long' /}
{param errorTexts: $errors ? $errors['password' + $index] : null /} {param errorTexts: $errors ? $errors['password' + $index] : null /}

2
src/test/java/com/englishtown/stash/hook/DefaultPasswordEncryptorTest.java → src/test/java/com/englishtown/bitbucket/hook/DefaultPasswordEncryptorTest.java

@ -1,4 +1,4 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.sal.api.pluginsettings.PluginSettings; import com.atlassian.sal.api.pluginsettings.PluginSettings;
import org.junit.Before; import org.junit.Before;

107
src/test/java/com/englishtown/bitbucket/hook/DefaultSettingsReflectionHelperTest.java

@ -0,0 +1,107 @@
package com.englishtown.bitbucket.hook;
import com.atlassian.bitbucket.setting.Settings;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link DefaultSettingsReflectionHelper}
*/
public class DefaultSettingsReflectionHelperTest {
@Test
public void testSet() throws Exception {
DefaultSettingsReflectionHelper helper = new DefaultSettingsReflectionHelper();
Map<String, Object> original = new HashMap<>();
original.put("old", "old");
TestSettings settings = new TestSettings(original);
Map<String, Object> values = new HashMap<>();
values.put("new", "new");
helper.set(values, settings);
assertNull(settings.getString("old"));
assertEquals("new", settings.getString("new"));
}
private static class TestSettings implements Settings {
private final Map<String, Object> values;
public TestSettings(Map<String, Object> values) {
this.values = ImmutableMap.copyOf(values);
}
@Nullable
@Override
public String getString(@Nonnull String key) {
return (String) values.get(key);
}
@Nonnull
@Override
public String getString(@Nonnull String key, @Nonnull String defaultValue) {
return null;
}
@Nullable
@Override
public Boolean getBoolean(@Nonnull String key) {
return null;
}
@Override
public boolean getBoolean(@Nonnull String key, boolean defaultValue) {
return false;
}
@Nullable
@Override
public Integer getInt(@Nonnull String key) {
return null;
}
@Override
public int getInt(@Nonnull String key, int defaultValue) {
return 0;
}
@Nullable
@Override
public Long getLong(@Nonnull String key) {
return null;
}
@Override
public long getLong(@Nonnull String key, long defaultValue) {
return 0;
}
@Nullable
@Override
public Double getDouble(@Nonnull String key) {
return null;
}
@Override
public double getDouble(@Nonnull String key, double defaultValue) {
return 0;
}
@Override
public Map<String, Object> asMap() {
return values;
}
}
}

108
src/test/java/com/englishtown/stash/hook/MirrorRepositoryHookTest.java → src/test/java/com/englishtown/bitbucket/hook/MirrorRepositoryHookTest.java

@ -1,21 +1,19 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.bitbucket.hook.repository.RepositoryHookContext;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.scm.CommandErrorHandler;
import com.atlassian.bitbucket.scm.CommandExitHandler;
import com.atlassian.bitbucket.scm.CommandOutputHandler;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.scm.git.command.GitCommand;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.setting.Settings;
import com.atlassian.bitbucket.setting.SettingsValidationErrors;
import com.atlassian.sal.api.pluginsettings.PluginSettings; import com.atlassian.sal.api.pluginsettings.PluginSettings;
import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory; import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import com.atlassian.stash.hook.repository.RepositoryHookContext;
import com.atlassian.stash.i18n.I18nService;
import com.atlassian.stash.repository.RefChange;
import com.atlassian.stash.repository.Repository;
import com.atlassian.stash.repository.RepositoryMetadataService;
import com.atlassian.stash.scm.CommandErrorHandler;
import com.atlassian.stash.scm.CommandExitHandler;
import com.atlassian.stash.scm.CommandOutputHandler;
import com.atlassian.stash.scm.git.GitCommand;
import com.atlassian.stash.scm.git.GitCommandBuilderFactory;
import com.atlassian.stash.scm.git.GitScm;
import com.atlassian.stash.scm.git.GitScmCommandBuilder;
import com.atlassian.stash.setting.Settings;
import com.atlassian.stash.setting.SettingsValidationErrors;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -25,12 +23,9 @@ import org.mockito.Matchers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -45,6 +40,9 @@ public class MirrorRepositoryHookTest {
private MirrorRepositoryHook hook; private MirrorRepositoryHook hook;
private GitScmCommandBuilder builder; private GitScmCommandBuilder builder;
@Mock
private ScmService scmService;
@Mock @Mock
private GitCommand<String> cmd; private GitCommand<String> cmd;
@Mock @Mock
@ -58,17 +56,16 @@ public class MirrorRepositoryHookTest {
@Mock @Mock
private PluginSettings pluginSettings; private PluginSettings pluginSettings;
@Mock @Mock
private RepositoryMetadataService repositoryMetadataService; private RepositoryService repositoryService;
private final String mirrorRepoUrlHttp = "https://stash-mirror.englishtown.com/scm/test/test.git"; private final String mirrorRepoUrlHttp = "https://bitbucket-mirror.englishtown.com/scm/test/test.git";
private final String mirrorRepoUrlSsh = "ssh://git@stash-mirror.englishtown.com/scm/test/test.git"; private final String mirrorRepoUrlSsh = "ssh://git@bitbucket-mirror.englishtown.com/scm/test/test.git";
private final String username = "test-user"; private final String username = "test-user";
private final String password = "test-password"; private final String password = "test-password";
private final String repository = "https://test-user:test-password@stash-mirror.englishtown.com/scm/test/test.git"; private final String repository = "https://test-user:test-password@bitbucket-mirror.englishtown.com/scm/test/test.git";
@SuppressWarnings("UnusedDeclaration")
@Captor @Captor
ArgumentCaptor<Callable<Void>> argumentCaptor; ArgumentCaptor<Runnable> argumentCaptor;
@Before @Before
public void setup() { public void setup() {
@ -78,18 +75,14 @@ public class MirrorRepositoryHookTest {
when(builder.argument(anyString())).thenReturn(builder); when(builder.argument(anyString())).thenReturn(builder);
when(builder.errorHandler(any(CommandErrorHandler.class))).thenReturn(builder); when(builder.errorHandler(any(CommandErrorHandler.class))).thenReturn(builder);
when(builder.exitHandler(any(CommandExitHandler.class))).thenReturn(builder); when(builder.exitHandler(any(CommandExitHandler.class))).thenReturn(builder);
when(builder.build(any(CommandOutputHandler.class))).thenReturn(cmd); when(builder.<String>build(any(CommandOutputHandler.class))).thenReturn(cmd);
GitCommandBuilderFactory builderFactory = mock(GitCommandBuilderFactory.class);
when(builderFactory.builder(any(Repository.class))).thenReturn(builder);
GitScm gitScm = mock(GitScm.class); doReturn(builder).when(scmService).createBuilder(any());
when(gitScm.getCommandBuilderFactory()).thenReturn(builderFactory);
when(pluginSettingsFactory.createSettingsForKey(anyString())).thenReturn(pluginSettings); when(pluginSettingsFactory.createSettingsForKey(anyString())).thenReturn(pluginSettings);
hook = new MirrorRepositoryHook(gitScm, mock(I18nService.class), executor, passwordEncryptor hook = new MirrorRepositoryHook(scmService, mock(I18nService.class), executor, passwordEncryptor
, settingsReflectionHelper, pluginSettingsFactory, repositoryMetadataService); , settingsReflectionHelper, pluginSettingsFactory, repositoryService);
} }
@ -100,27 +93,26 @@ public class MirrorRepositoryHookTest {
Repository repo = mock(Repository.class); Repository repo = mock(Repository.class);
when(repo.getName()).thenReturn("test"); when(repo.getName()).thenReturn("test");
hook.postReceive(buildContext(repo), new ArrayList<RefChange>()); hook.postReceive(buildContext(repo), new ArrayList<>());
verifyExecutor(); verifyExecutor();
} }
@Test @Test
public void testEmptyRepositoriesNotMirrored() { public void testEmptyRepositoriesNotMirrored() {
Repository repo = mock(Repository.class); Repository repo = mock(Repository.class);
when(repositoryMetadataService.isEmpty(repo)).thenReturn(true); when(repositoryService.isEmpty(repo)).thenReturn(true);
hook.postReceive(buildContext(repo), new ArrayList<RefChange>()); hook.postReceive(buildContext(repo), new ArrayList<>());
verify(executor, never()).submit(Matchers.<Callable<Object>>any()); verify(executor, never()).submit(Matchers.<Runnable>any());
} }
@Test @Test
public void testRunMirrorCommand_Retries() throws Exception { public void testRunMirrorCommand_Retries() throws Exception {
GitScm gitScm = mock(GitScm.class); when(scmService.createBuilder(any())).thenThrow(new RuntimeException("Intentional unit test exception"));
when(gitScm.getCommandBuilderFactory()).thenThrow(new RuntimeException("Intentional unit test exception")); MirrorRepositoryHook hook = new MirrorRepositoryHook(scmService, mock(I18nService.class), executor,
MirrorRepositoryHook hook = new MirrorRepositoryHook(gitScm, mock(I18nService.class), executor, passwordEncryptor, settingsReflectionHelper, pluginSettingsFactory, repositoryService);
passwordEncryptor, settingsReflectionHelper, pluginSettingsFactory, repositoryMetadataService);
MirrorRepositoryHook.MirrorSettings ms = new MirrorRepositoryHook.MirrorSettings(); MirrorRepositoryHook.MirrorSettings ms = new MirrorRepositoryHook.MirrorSettings();
ms.mirrorRepoUrl = mirrorRepoUrlHttp; ms.mirrorRepoUrl = mirrorRepoUrlHttp;
ms.username = username; ms.username = username;
@ -128,29 +120,29 @@ public class MirrorRepositoryHookTest {
hook.runMirrorCommand(ms, mock(Repository.class)); hook.runMirrorCommand(ms, mock(Repository.class));
verify(executor).submit(argumentCaptor.capture()); verify(executor).submit(argumentCaptor.capture());
Callable<Void> callable = argumentCaptor.getValue(); Runnable runnable = argumentCaptor.getValue();
callable.call(); runnable.run();
verify(executor, times(1)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); verify(executor, times(1)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class));
callable = argumentCaptor.getValue(); runnable = argumentCaptor.getValue();
callable.call(); runnable.run();
verify(executor, times(2)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); verify(executor, times(2)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class));
callable = argumentCaptor.getValue(); runnable = argumentCaptor.getValue();
callable.call(); runnable.run();
verify(executor, times(3)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); verify(executor, times(3)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class));
callable = argumentCaptor.getValue(); runnable = argumentCaptor.getValue();
callable.call(); runnable.run();
verify(executor, times(4)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); verify(executor, times(4)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class));
callable = argumentCaptor.getValue(); runnable = argumentCaptor.getValue();
callable.call(); runnable.run();
// Make sure it is only called 5 times // Make sure it is only called 5 times
callable.call(); runnable.run();
callable.call(); runnable.run();
callable.call(); runnable.run();
verify(executor, times(4)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); verify(executor, times(4)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class));
} }
@ -158,8 +150,8 @@ public class MirrorRepositoryHookTest {
private void verifyExecutor() throws Exception { private void verifyExecutor() throws Exception {
verify(executor).submit(argumentCaptor.capture()); verify(executor).submit(argumentCaptor.capture());
Callable<Void> callable = argumentCaptor.getValue(); Runnable runnable = argumentCaptor.getValue();
callable.call(); runnable.run();
verify(builder, times(1)).command(eq("push")); verify(builder, times(1)).command(eq("push"));
verify(builder, times(1)).argument(eq("--prune")); verify(builder, times(1)).argument(eq("--prune"));
@ -193,8 +185,8 @@ public class MirrorRepositoryHookTest {
.thenReturn("") .thenReturn("")
.thenReturn(mirrorRepoUrlHttp) .thenReturn(mirrorRepoUrlHttp)
.thenReturn("invalid uri") .thenReturn("invalid uri")
.thenReturn("http://should-not:have-user@stash-mirror.englishtown.com/scm/test/test.git") .thenReturn("http://should-not:have-user@bitbucket-mirror.englishtown.com/scm/test/test.git")
.thenReturn("ssh://user@stash-mirror.englishtown.com/scm/test/test.git") .thenReturn("ssh://user@bitbucket-mirror.englishtown.com/scm/test/test.git")
.thenReturn(mirrorRepoUrlSsh) .thenReturn(mirrorRepoUrlSsh)
.thenReturn(mirrorRepoUrlHttp); .thenReturn(mirrorRepoUrlHttp);

4
src/test/java/com/englishtown/stash/hook/PasswordHandlerTest.java → src/test/java/com/englishtown/bitbucket/hook/PasswordHandlerTest.java

@ -1,6 +1,6 @@
package com.englishtown.stash.hook; package com.englishtown.bitbucket.hook;
import com.atlassian.stash.scm.CommandExitHandler; import com.atlassian.bitbucket.scm.CommandExitHandler;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

36
src/test/java/com/englishtown/stash/hook/DefaultSettingsReflectionHelperTest.java

@ -1,36 +0,0 @@
package com.englishtown.stash.hook;
import com.atlassian.stash.internal.setting.MapSettingsBuilder;
import com.atlassian.stash.setting.Settings;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link DefaultSettingsReflectionHelper}
*/
public class DefaultSettingsReflectionHelperTest {
@Test
public void testSet() throws Exception {
DefaultSettingsReflectionHelper helper = new DefaultSettingsReflectionHelper();
MapSettingsBuilder builder = new MapSettingsBuilder();
Map<String, Object> values = new HashMap<String, Object>();
builder.add("old", "old");
values.put("new", "new");
Settings settings = builder.build();
helper.set(values, settings);
assertNull(settings.getString("old"));
assertEquals("new", settings.getString("new"));
}
}
Loading…
Cancel
Save