Adrian Gonzalez
12 years ago
commit
1f0025c66e
12 changed files with 705 additions and 0 deletions
@ -0,0 +1,20 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
Copyright © 2013 Englishtown <opensource@englishtown.com> |
||||||
|
|
||||||
|
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. |
@ -0,0 +1,14 @@ |
|||||||
|
#Stash Repository Hook for Mirroring |
||||||
|
|
||||||
|
The following is a plugin for Atlassian Stash to provide repository mirroring to a remote repository. |
||||||
|
|
||||||
|
|
||||||
|
* atlas-run -- installs this plugin into the product and starts it on localhost |
||||||
|
* atlas-debug -- same as atlas-run, but allows a debugger to attach at port 5005 |
||||||
|
* atlas-cli -- after atlas-run or atlas-debug, opens a Maven command line window: |
||||||
|
- 'pi' reinstalls the plugin into the running product instance |
||||||
|
* atlas-help -- prints description for all commands in the SDK |
||||||
|
|
||||||
|
Full documentation is always available at: |
||||||
|
|
||||||
|
https://developer.atlassian.com/display/DOCS/Introduction+to+the+Atlassian+Plugin+SDK |
@ -0,0 +1,185 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
|
||||||
|
<groupId>com.englishtown</groupId> |
||||||
|
<artifactId>stash-hook-mirror</artifactId> |
||||||
|
<version>1.0.0-SNAPSHOT</version> |
||||||
|
|
||||||
|
<organization> |
||||||
|
<name>Englishtown</name> |
||||||
|
<url>http://www.englishtown.com/</url> |
||||||
|
</organization> |
||||||
|
|
||||||
|
<name>Stash Mirror Plugin</name> |
||||||
|
<description>A stash repository hook for mirroring to a remote repository.</description> |
||||||
|
<packaging>atlassian-plugin</packaging> |
||||||
|
|
||||||
|
<properties> |
||||||
|
<stash.version>2.3.1</stash.version> |
||||||
|
<stash.data.version>2.3.1</stash.data.version> |
||||||
|
<amps.version>4.1.7</amps.version> |
||||||
|
<plugin.testrunner.version>1.1.1</plugin.testrunner.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> |
||||||
|
|
||||||
|
<dependencyManagement> |
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>com.atlassian.stash</groupId> |
||||||
|
<artifactId>stash-parent</artifactId> |
||||||
|
<version>${stash.version}</version> |
||||||
|
<type>pom</type> |
||||||
|
<scope>import</scope> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
</dependencyManagement> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>com.atlassian.stash</groupId> |
||||||
|
<artifactId>stash-scm-git-api</artifactId> |
||||||
|
<scope>provided</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.slf4j</groupId> |
||||||
|
<artifactId>slf4j-api</artifactId> |
||||||
|
<version>${slf4j.version}</version> |
||||||
|
<scope>provided</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>com.atlassian.stash</groupId> |
||||||
|
<artifactId>stash-scm-git</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>com.atlassian.stash</groupId> |
||||||
|
<artifactId>stash-api</artifactId> |
||||||
|
<scope>provided</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>com.atlassian.stash</groupId> |
||||||
|
<artifactId>stash-spi</artifactId> |
||||||
|
<scope>provided</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>com.atlassian.stash</groupId> |
||||||
|
<artifactId>stash-page-objects</artifactId> |
||||||
|
<scope>provided</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>javax.servlet</groupId> |
||||||
|
<artifactId>servlet-api</artifactId> |
||||||
|
<scope>provided</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>junit</groupId> |
||||||
|
<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.plugins</groupId> |
||||||
|
<artifactId>atlassian-plugins-osgi-testrunner</artifactId> |
||||||
|
<version>${plugin.testrunner.version}</version> |
||||||
|
<scope>test</scope> |
||||||
|
</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> |
||||||
|
<groupId>org.mockito</groupId> |
||||||
|
<artifactId>mockito-all</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> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
|
||||||
|
<build> |
||||||
|
<plugins> |
||||||
|
<plugin> |
||||||
|
<groupId>com.atlassian.maven.plugins</groupId> |
||||||
|
<artifactId>maven-stash-plugin</artifactId> |
||||||
|
<version>${amps.version}</version> |
||||||
|
<extensions>true</extensions> |
||||||
|
<configuration> |
||||||
|
<products> |
||||||
|
<product> |
||||||
|
<id>stash</id> |
||||||
|
<instanceId>stash</instanceId> |
||||||
|
<version>${stash.version}</version> |
||||||
|
<dataVersion>${stash.data.version}</dataVersion> |
||||||
|
</product> |
||||||
|
</products> |
||||||
|
</configuration> |
||||||
|
</plugin> |
||||||
|
<plugin> |
||||||
|
<artifactId>maven-compiler-plugin</artifactId> |
||||||
|
<version>${plugin.compiler.version}</version> |
||||||
|
<configuration> |
||||||
|
<source>1.6</source> |
||||||
|
<target>1.6</target> |
||||||
|
</configuration> |
||||||
|
</plugin> |
||||||
|
</plugins> |
||||||
|
</build> |
||||||
|
|
||||||
|
<repositories> |
||||||
|
<repository> |
||||||
|
<id>atlassian-public</id> |
||||||
|
<url>https://m2proxy.atlassian.com/repository/public</url> |
||||||
|
<snapshots> |
||||||
|
<enabled>true</enabled> |
||||||
|
<updatePolicy>daily</updatePolicy> |
||||||
|
<checksumPolicy>warn</checksumPolicy> |
||||||
|
</snapshots> |
||||||
|
<releases> |
||||||
|
<enabled>true</enabled> |
||||||
|
<checksumPolicy>warn</checksumPolicy> |
||||||
|
</releases> |
||||||
|
</repository> |
||||||
|
</repositories> |
||||||
|
|
||||||
|
<pluginRepositories> |
||||||
|
<pluginRepository> |
||||||
|
<id>atlassian-public</id> |
||||||
|
<url>https://m2proxy.atlassian.com/repository/public</url> |
||||||
|
<releases> |
||||||
|
<enabled>true</enabled> |
||||||
|
<checksumPolicy>warn</checksumPolicy> |
||||||
|
</releases> |
||||||
|
<snapshots> |
||||||
|
<checksumPolicy>warn</checksumPolicy> |
||||||
|
</snapshots> |
||||||
|
</pluginRepository> |
||||||
|
</pluginRepositories> |
||||||
|
|
||||||
|
</project> |
@ -0,0 +1,151 @@ |
|||||||
|
package com.englishtown.stash.hook; |
||||||
|
|
||||||
|
import com.atlassian.stash.hook.repository.AsyncPostReceiveRepositoryHook; |
||||||
|
import com.atlassian.stash.hook.repository.RepositoryHookContext; |
||||||
|
import com.atlassian.stash.i18n.I18nService; |
||||||
|
import com.atlassian.stash.internal.scm.git.GitCommandExitHandler; |
||||||
|
import com.atlassian.stash.repository.RefChange; |
||||||
|
import com.atlassian.stash.repository.Repository; |
||||||
|
import com.atlassian.stash.scm.CommandExitHandler; |
||||||
|
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.LoggerFactory; |
||||||
|
|
||||||
|
import javax.annotation.Nonnull; |
||||||
|
import java.net.URI; |
||||||
|
import java.net.URISyntaxException; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator { |
||||||
|
|
||||||
|
static final String SETTING_MIRROR_REPO_URL = "mirrorRepoUrl"; |
||||||
|
static final String SETTING_USERNAME = "username"; |
||||||
|
static final String SETTING_PASSWORD = "password"; |
||||||
|
|
||||||
|
private final GitScm gitScm; |
||||||
|
private final I18nService i18nService; |
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MirrorRepositoryHook.class); |
||||||
|
|
||||||
|
public MirrorRepositoryHook(GitScm gitScm, I18nService i18nService) { |
||||||
|
this.gitScm = gitScm; |
||||||
|
this.i18nService = i18nService; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls the remote stash instance(s) to push the latest changes |
||||||
|
* <p/> |
||||||
|
* 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. |
||||||
|
* <p/> |
||||||
|
* 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 refChanges the refs that have just been updated |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void postReceive( |
||||||
|
@Nonnull RepositoryHookContext context, |
||||||
|
@Nonnull Collection<RefChange> refChanges) { |
||||||
|
|
||||||
|
try { |
||||||
|
logger.debug("MirrorRepositoryHook: postReceive started."); |
||||||
|
|
||||||
|
Settings settings = context.getSettings(); |
||||||
|
String mirrorRepoUrl = settings.getString(SETTING_MIRROR_REPO_URL); |
||||||
|
String username = settings.getString(SETTING_USERNAME); |
||||||
|
String password = settings.getString(SETTING_PASSWORD); |
||||||
|
|
||||||
|
URI authenticatedUrl = getAuthenticatedUrl(mirrorRepoUrl, username, password); |
||||||
|
GitScmCommandBuilder builder = gitScm.getCommandBuilderFactory().builder(context.getRepository()); |
||||||
|
CommandExitHandler exitHandler = new GitCommandExitHandler(i18nService, context.getRepository()); |
||||||
|
PasswordHandler passwordHandler = new PasswordHandler(password, exitHandler); |
||||||
|
|
||||||
|
// Call push command with the mirror flag set
|
||||||
|
String result = builder |
||||||
|
.command("push") |
||||||
|
.argument("--mirror") |
||||||
|
.argument(authenticatedUrl.toString()) |
||||||
|
.errorHandler(passwordHandler) |
||||||
|
.exitHandler(passwordHandler) |
||||||
|
.build(passwordHandler) |
||||||
|
.call(); |
||||||
|
|
||||||
|
builder.defaultExitHandler(); |
||||||
|
logger.debug("MirrorRepositoryHook: postReceive completed with result '{}'.", result); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
logger.error("MirrorRepositoryHook: Error running mirror hook", e); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
URI getAuthenticatedUrl(String mirrorRepoUrl, String username, String password) throws URISyntaxException { |
||||||
|
|
||||||
|
URI uri = URI.create(mirrorRepoUrl); |
||||||
|
String userInfo = username + ":" + password; |
||||||
|
|
||||||
|
return new URI(uri.getScheme(), userInfo, uri.getHost(), uri.getPort(), |
||||||
|
uri.getPath(), uri.getQuery(), uri.getFragment()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Validate the given {@code settings} before they are persisted. |
||||||
|
* |
||||||
|
* @param settings to be validated |
||||||
|
* @param errors callback for reporting validation errors. |
||||||
|
* @param repository the context {@code Repository} the settings will be associated with |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void validate( |
||||||
|
@Nonnull Settings settings, |
||||||
|
@Nonnull SettingsValidationErrors errors, |
||||||
|
@Nonnull Repository repository) { |
||||||
|
|
||||||
|
try { |
||||||
|
int count = 0; |
||||||
|
logger.debug("MirrorRepositoryHook: validate started."); |
||||||
|
|
||||||
|
String mirrorRepoUrl = settings.getString(SETTING_MIRROR_REPO_URL, ""); |
||||||
|
if (mirrorRepoUrl.isEmpty()) { |
||||||
|
count++; |
||||||
|
errors.addFieldError(SETTING_MIRROR_REPO_URL, "The mirror repo url is required."); |
||||||
|
} else { |
||||||
|
URI uri; |
||||||
|
try { |
||||||
|
uri = URI.create(mirrorRepoUrl); |
||||||
|
if (!uri.getScheme().toLowerCase().startsWith("http") || mirrorRepoUrl.contains("@")) { |
||||||
|
count++; |
||||||
|
errors.addFieldError(SETTING_MIRROR_REPO_URL, "The mirror repo url must be a valid http(s) " + |
||||||
|
"URI and the user should be specified separately."); |
||||||
|
} |
||||||
|
} catch (Exception ex) { |
||||||
|
count++; |
||||||
|
errors.addFieldError(SETTING_MIRROR_REPO_URL, "The mirror repo url must be a valid http(s) URI."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (settings.getString(SETTING_USERNAME, "").isEmpty()) { |
||||||
|
count++; |
||||||
|
errors.addFieldError(SETTING_USERNAME, "The username is required."); |
||||||
|
} |
||||||
|
|
||||||
|
if (settings.getString(SETTING_PASSWORD, "").isEmpty()) { |
||||||
|
count++; |
||||||
|
errors.addFieldError(SETTING_PASSWORD, "The password is required."); |
||||||
|
} |
||||||
|
|
||||||
|
logger.debug("MirrorRepositoryHook: validate completed with {} error(s).", count); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
logger.error("Error running MirrorRepositoryHook validate.", e); |
||||||
|
errors.addFormError(e.getMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package com.englishtown.stash.hook; |
||||||
|
|
||||||
|
import com.atlassian.stash.scm.CommandErrorHandler; |
||||||
|
import com.atlassian.stash.scm.CommandExitHandler; |
||||||
|
import com.atlassian.stash.scm.CommandOutputHandler; |
||||||
|
import com.atlassian.utils.process.StringOutputHandler; |
||||||
|
|
||||||
|
import javax.annotation.Nonnull; |
||||||
|
import javax.annotation.Nullable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Handles removing passwords from output text |
||||||
|
*/ |
||||||
|
class PasswordHandler extends StringOutputHandler |
||||||
|
implements CommandOutputHandler<String>, CommandErrorHandler, CommandExitHandler { |
||||||
|
|
||||||
|
private final String target; |
||||||
|
private final CommandExitHandler exitHandler; |
||||||
|
|
||||||
|
private static final String PASSWORD_REPLACEMENT = ":*****@"; |
||||||
|
|
||||||
|
public PasswordHandler(String password, CommandExitHandler exitHandler) { |
||||||
|
this.exitHandler = exitHandler; |
||||||
|
this.target = ":" + password + "@"; |
||||||
|
} |
||||||
|
|
||||||
|
public String cleanText(String text) { |
||||||
|
if (text == null || text.isEmpty()) { |
||||||
|
return text; |
||||||
|
} |
||||||
|
return text.replace(target, PASSWORD_REPLACEMENT); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getOutput() { |
||||||
|
return cleanText(super.getOutput()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCancel(@Nonnull String command, int exitCode, @Nullable String stdErr, @Nullable Throwable thrown) { |
||||||
|
exitHandler.onCancel(cleanText(command), exitCode, cleanText(stdErr), thrown); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onExit(@Nonnull String command, int exitCode, @Nullable String stdErr, @Nullable Throwable thrown) { |
||||||
|
exitHandler.onExit(cleanText(command), exitCode, cleanText(stdErr), thrown); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,29 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
|
||||||
|
<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2"> |
||||||
|
<plugin-info> |
||||||
|
<description>${project.description}</description> |
||||||
|
<version>${project.version}</version> |
||||||
|
<vendor name="${project.organization.name}" url="${project.organization.url}"/> |
||||||
|
</plugin-info> |
||||||
|
|
||||||
|
<!-- Components that are injected --> |
||||||
|
<component-import key="gitScm" interface="com.atlassian.stash.scm.git.GitScm" /> |
||||||
|
<component-import key="i18nService" interface="com.atlassian.stash.i18n.I18nService" /> |
||||||
|
|
||||||
|
<!-- add our i18n resource --> |
||||||
|
<resource type="i18n" name="i18n" location="stash-hook-mirror"/> |
||||||
|
|
||||||
|
<!-- import from the product container --> |
||||||
|
<component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/> |
||||||
|
|
||||||
|
<repository-hook name="Mirror Repository Hook" i18n-name-key="mirror-repository-hook.name" |
||||||
|
key="mirror-repository-hook" class="com.englishtown.stash.hook.MirrorRepositoryHook"> |
||||||
|
<description key="mirror-repository-hook.description">Mirror Repository Hook</description> |
||||||
|
<icon>/icons/mirror-icon.png</icon> |
||||||
|
<config-form name="Mirror Hook Config" key="mirror-repository-hook-config"> |
||||||
|
<view>com.englishtown.stash.hook.mirrorrepositoryhook.view</view> |
||||||
|
<directory location="/static/"/> |
||||||
|
</config-form> |
||||||
|
</repository-hook> |
||||||
|
</atlassian-plugin> |
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,4 @@ |
|||||||
|
#put any key/value pairs here |
||||||
|
my.plugin.name=stash-hook-mirror |
||||||
|
mirror-repository-hook.name=Mirror Hook |
||||||
|
mirror-repository-hook.description=Mirrors a stash repository to a remote repository. |
@ -0,0 +1,44 @@ |
|||||||
|
{namespace com.englishtown.stash.hook.mirrorrepositoryhook} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param config |
||||||
|
* @param? errors |
||||||
|
*/ |
||||||
|
{template .view} |
||||||
|
{call aui.form.textField} |
||||||
|
{param id: 'mirrorRepoUrl' /} |
||||||
|
{param value: $config['mirrorRepoUrl'] /} |
||||||
|
{param labelContent} |
||||||
|
{stash_i18n('com.englishtown.stash.hook.mirror.strings.mirrorRepoUrl.label', 'Mirror Repo URL')} |
||||||
|
{/param} |
||||||
|
{param isRequired: true /} |
||||||
|
{param descriptionText: stash_i18n('com.englishtown.stash.hook.mirror.strings.mirrorRepoUrl.description', |
||||||
|
'URL to the remote Stash mirrored repo') /} |
||||||
|
{param extraClasses: 'long' /} |
||||||
|
{param errorTexts: $errors ? $errors['mirrorRepoUrl'] : null /} |
||||||
|
{/call} |
||||||
|
{call aui.form.textField} |
||||||
|
{param id: 'username' /} |
||||||
|
{param value: $config['username'] /} |
||||||
|
{param labelContent} |
||||||
|
{stash_i18n('com.englishtown.stash.hook.mirror.strings.username.label', 'Username')} |
||||||
|
{/param} |
||||||
|
{param isRequired: true /} |
||||||
|
{param descriptionText: stash_i18n('com.englishtown.stash.hook.mirror.strings.username.description', |
||||||
|
'The username to use for pushing to the mirror') /} |
||||||
|
{param extraClasses: 'long' /} |
||||||
|
{param errorTexts: $errors ? $errors['username'] : null /} |
||||||
|
{/call} |
||||||
|
{call aui.form.passwordField} |
||||||
|
{param id: 'password' /} |
||||||
|
{param value: $config['password'] /} |
||||||
|
{param labelContent} |
||||||
|
{stash_i18n('com.englishtown.stash.hook.mirror.strings.password.label', 'Password')} |
||||||
|
{/param} |
||||||
|
{param isRequired: true /} |
||||||
|
{param descriptionText: stash_i18n('com.englishtown.stash.hook.mirror.strings.password.description', |
||||||
|
'The password to use for pushing to the mirror') /} |
||||||
|
{param extraClasses: 'long' /} |
||||||
|
{param errorTexts: $errors ? $errors['password'] : null /} |
||||||
|
{/call} |
||||||
|
{/template} |
@ -0,0 +1,146 @@ |
|||||||
|
package com.englishtown.stash.hook; |
||||||
|
|
||||||
|
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.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.Test; |
||||||
|
|
||||||
|
import java.net.URI; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link MirrorRepositoryHook} |
||||||
|
*/ |
||||||
|
public class MirrorRepositoryHookTest { |
||||||
|
|
||||||
|
private MirrorRepositoryHook hook; |
||||||
|
private GitScmCommandBuilder builder; |
||||||
|
private GitCommand<String> cmd; |
||||||
|
|
||||||
|
private final String mirrorRepoUrl = "https://stash-mirror.englishtown.com/scm/test/test.git"; |
||||||
|
private final String username = "test-user"; |
||||||
|
private final String password = "test-password"; |
||||||
|
private final String repository = "https://test-user:test-password@stash-mirror.englishtown.com/scm/test/test.git"; |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
|
||||||
|
cmd = mock(GitCommand.class); |
||||||
|
builder = mock(GitScmCommandBuilder.class); |
||||||
|
when(builder.command(anyString())).thenReturn(builder); |
||||||
|
when(builder.argument(anyString())).thenReturn(builder); |
||||||
|
when(builder.errorHandler(any(CommandErrorHandler.class))).thenReturn(builder); |
||||||
|
when(builder.exitHandler(any(CommandExitHandler.class))).thenReturn(builder); |
||||||
|
when(builder.build(any(CommandOutputHandler.class))).thenReturn(cmd); |
||||||
|
|
||||||
|
GitCommandBuilderFactory builderFactory = mock(GitCommandBuilderFactory.class); |
||||||
|
when(builderFactory.builder(any(Repository.class))).thenReturn(builder); |
||||||
|
|
||||||
|
GitScm gitScm = mock(GitScm.class); |
||||||
|
when(gitScm.getCommandBuilderFactory()).thenReturn(builderFactory); |
||||||
|
|
||||||
|
hook = new MirrorRepositoryHook(gitScm, mock(I18nService.class)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testPostReceive() throws Exception { |
||||||
|
|
||||||
|
Settings settings = mock(Settings.class); |
||||||
|
when(settings.getString(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL))).thenReturn(mirrorRepoUrl); |
||||||
|
when(settings.getString(eq(MirrorRepositoryHook.SETTING_USERNAME))).thenReturn(username); |
||||||
|
when(settings.getString(eq(MirrorRepositoryHook.SETTING_PASSWORD))).thenReturn(password); |
||||||
|
|
||||||
|
RepositoryHookContext context = mock(RepositoryHookContext.class); |
||||||
|
when(context.getSettings()).thenReturn(settings); |
||||||
|
|
||||||
|
Collection<RefChange> refChanges = new ArrayList<RefChange>(); |
||||||
|
|
||||||
|
hook.postReceive(context, refChanges); |
||||||
|
verify(builder, times(1)).command(eq("push")); |
||||||
|
verify(builder, times(1)).argument(eq("--mirror")); |
||||||
|
verify(builder, times(1)).argument(eq(repository)); |
||||||
|
verify(cmd, times(1)).call(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testGetAuthenticatedUrl() throws Exception { |
||||||
|
|
||||||
|
URI result; |
||||||
|
|
||||||
|
result = hook.getAuthenticatedUrl(mirrorRepoUrl, username, password); |
||||||
|
assertEquals(repository, result.toString()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testValidate() throws Exception { |
||||||
|
|
||||||
|
Settings settings = mock(Settings.class); |
||||||
|
|
||||||
|
when(settings.getString(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL), eq(""))) |
||||||
|
.thenThrow(new RuntimeException()) |
||||||
|
.thenReturn("") |
||||||
|
.thenReturn("invalid uri") |
||||||
|
.thenReturn("http://should-not:have-user@stash-mirror.englishtown.com/scm/test/test.git") |
||||||
|
.thenReturn(mirrorRepoUrl); |
||||||
|
|
||||||
|
when(settings.getString(eq(MirrorRepositoryHook.SETTING_USERNAME), eq(""))) |
||||||
|
.thenReturn("") |
||||||
|
.thenReturn(username); |
||||||
|
|
||||||
|
when(settings.getString(eq(MirrorRepositoryHook.SETTING_PASSWORD), eq(""))) |
||||||
|
.thenReturn("") |
||||||
|
.thenReturn(password); |
||||||
|
|
||||||
|
Repository repo = mock(Repository.class); |
||||||
|
SettingsValidationErrors errors; |
||||||
|
|
||||||
|
errors = mock(SettingsValidationErrors.class); |
||||||
|
hook.validate(settings, errors, repo); |
||||||
|
verify(errors, times(1)).addFormError(anyString()); |
||||||
|
|
||||||
|
errors = mock(SettingsValidationErrors.class); |
||||||
|
hook.validate(settings, errors, repo); |
||||||
|
verify(errors, never()).addFormError(anyString()); |
||||||
|
verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL), anyString()); |
||||||
|
verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_USERNAME), anyString()); |
||||||
|
verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_PASSWORD), anyString()); |
||||||
|
|
||||||
|
errors = mock(SettingsValidationErrors.class); |
||||||
|
hook.validate(settings, errors, repo); |
||||||
|
verify(errors, never()).addFormError(anyString()); |
||||||
|
verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL), anyString()); |
||||||
|
verify(errors).addFieldError(anyString(), anyString()); |
||||||
|
|
||||||
|
errors = mock(SettingsValidationErrors.class); |
||||||
|
hook.validate(settings, errors, repo); |
||||||
|
verify(errors, never()).addFormError(anyString()); |
||||||
|
verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL), anyString()); |
||||||
|
verify(errors).addFieldError(anyString(), anyString()); |
||||||
|
|
||||||
|
errors = mock(SettingsValidationErrors.class); |
||||||
|
hook.validate(settings, errors, repo); |
||||||
|
verify(errors, never()).addFormError(anyString()); |
||||||
|
verify(errors, never()).addFieldError(anyString(), anyString()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
package com.englishtown.stash.hook; |
||||||
|
|
||||||
|
import com.atlassian.stash.scm.CommandExitHandler; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link PasswordHandler} |
||||||
|
*/ |
||||||
|
public class PasswordHandlerTest { |
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal") |
||||||
|
private final String password = "pwd@123"; |
||||||
|
private final String secretText = "https://test.user:pwd@123@test.englishtown.com/scm/test/test.git"; |
||||||
|
private final String cleanedText = "https://test.user:*****@test.englishtown.com/scm/test/test.git"; |
||||||
|
private CommandExitHandler exitHandler; |
||||||
|
private PasswordHandler handler; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() throws Exception { |
||||||
|
exitHandler = mock(CommandExitHandler.class); |
||||||
|
handler = new PasswordHandler(password, exitHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCleanText() throws Exception { |
||||||
|
|
||||||
|
String result = handler.cleanText(secretText); |
||||||
|
assertEquals(cleanedText, result); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testGetOutput() throws Exception { |
||||||
|
|
||||||
|
String result = handler.getOutput(); |
||||||
|
assertEquals("", result); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testOnCancel() throws Exception { |
||||||
|
|
||||||
|
handler.onCancel(secretText, 0, secretText, null); |
||||||
|
verify(exitHandler).onCancel(eq(cleanedText), eq(0), eq(cleanedText), any(Throwable.class)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testOnExit() throws Exception { |
||||||
|
|
||||||
|
handler.onExit(secretText, 0, secretText, null); |
||||||
|
verify(exitHandler).onExit(eq(cleanedText), eq(0), eq(cleanedText), any(Throwable.class)); |
||||||
|
|
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue