Skip to content

Commit 1f0025c

Browse files
Initial commit
0 parents  commit 1f0025c

12 files changed

+705
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
\.idea/
2+
*.iml
3+
target/

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
Copyright © 2013 Englishtown <[email protected]>
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the “Software”), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in
12+
all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#Stash Repository Hook for Mirroring
2+
3+
The following is a plugin for Atlassian Stash to provide repository mirroring to a remote repository.
4+
5+
6+
* atlas-run -- installs this plugin into the product and starts it on localhost
7+
* atlas-debug -- same as atlas-run, but allows a debugger to attach at port 5005
8+
* atlas-cli -- after atlas-run or atlas-debug, opens a Maven command line window:
9+
- 'pi' reinstalls the plugin into the running product instance
10+
* atlas-help -- prints description for all commands in the SDK
11+
12+
Full documentation is always available at:
13+
14+
https://developer.atlassian.com/display/DOCS/Introduction+to+the+Atlassian+Plugin+SDK

pom.xml

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.englishtown</groupId>
8+
<artifactId>stash-hook-mirror</artifactId>
9+
<version>1.0.0-SNAPSHOT</version>
10+
11+
<organization>
12+
<name>Englishtown</name>
13+
<url>http://www.englishtown.com/</url>
14+
</organization>
15+
16+
<name>Stash Mirror Plugin</name>
17+
<description>A stash repository hook for mirroring to a remote repository.</description>
18+
<packaging>atlassian-plugin</packaging>
19+
20+
<properties>
21+
<stash.version>2.3.1</stash.version>
22+
<stash.data.version>2.3.1</stash.data.version>
23+
<amps.version>4.1.7</amps.version>
24+
<plugin.testrunner.version>1.1.1</plugin.testrunner.version>
25+
26+
<junit.version>4.10</junit.version>
27+
<common-lang.version>2.6</common-lang.version>
28+
<plugin.compiler.version>3.1</plugin.compiler.version>
29+
<mockito.version>1.8.5</mockito.version>
30+
<gson.version>2.2.2-atlassian-1</gson.version>
31+
<jsr311.version>1.1.1</jsr311.version>
32+
<slf4j.version>1.7.5</slf4j.version>
33+
</properties>
34+
35+
<dependencyManagement>
36+
<dependencies>
37+
<dependency>
38+
<groupId>com.atlassian.stash</groupId>
39+
<artifactId>stash-parent</artifactId>
40+
<version>${stash.version}</version>
41+
<type>pom</type>
42+
<scope>import</scope>
43+
</dependency>
44+
</dependencies>
45+
</dependencyManagement>
46+
47+
<dependencies>
48+
<dependency>
49+
<groupId>com.atlassian.stash</groupId>
50+
<artifactId>stash-scm-git-api</artifactId>
51+
<scope>provided</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.slf4j</groupId>
55+
<artifactId>slf4j-api</artifactId>
56+
<version>${slf4j.version}</version>
57+
<scope>provided</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>com.atlassian.stash</groupId>
61+
<artifactId>stash-scm-git</artifactId>
62+
</dependency>
63+
<dependency>
64+
<groupId>com.atlassian.stash</groupId>
65+
<artifactId>stash-api</artifactId>
66+
<scope>provided</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>com.atlassian.stash</groupId>
70+
<artifactId>stash-spi</artifactId>
71+
<scope>provided</scope>
72+
</dependency>
73+
<dependency>
74+
<groupId>com.atlassian.stash</groupId>
75+
<artifactId>stash-page-objects</artifactId>
76+
<scope>provided</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>javax.servlet</groupId>
80+
<artifactId>servlet-api</artifactId>
81+
<scope>provided</scope>
82+
</dependency>
83+
<dependency>
84+
<groupId>junit</groupId>
85+
<artifactId>junit</artifactId>
86+
<version>${junit.version}</version>
87+
<scope>test</scope>
88+
</dependency>
89+
<dependency>
90+
<groupId>commons-lang</groupId>
91+
<artifactId>commons-lang</artifactId>
92+
<version>${common-lang.version}</version>
93+
</dependency>
94+
<!-- WIRED TEST RUNNER DEPENDENCIES -->
95+
<dependency>
96+
<groupId>com.atlassian.plugins</groupId>
97+
<artifactId>atlassian-plugins-osgi-testrunner</artifactId>
98+
<version>${plugin.testrunner.version}</version>
99+
<scope>test</scope>
100+
</dependency>
101+
<dependency>
102+
<groupId>javax.ws.rs</groupId>
103+
<artifactId>jsr311-api</artifactId>
104+
<version>${jsr311.version}</version>
105+
<scope>provided</scope>
106+
</dependency>
107+
<dependency>
108+
<groupId>com.google.code.gson</groupId>
109+
<artifactId>gson</artifactId>
110+
<version>${gson.version}</version>
111+
</dependency>
112+
<dependency>
113+
<groupId>org.mockito</groupId>
114+
<artifactId>mockito-all</artifactId>
115+
<version>${mockito.version}</version>
116+
<scope>test</scope>
117+
</dependency>
118+
<dependency>
119+
<groupId>org.slf4j</groupId>
120+
<artifactId>slf4j-simple</artifactId>
121+
<version>${slf4j.version}</version>
122+
<scope>test</scope>
123+
</dependency>
124+
</dependencies>
125+
126+
<build>
127+
<plugins>
128+
<plugin>
129+
<groupId>com.atlassian.maven.plugins</groupId>
130+
<artifactId>maven-stash-plugin</artifactId>
131+
<version>${amps.version}</version>
132+
<extensions>true</extensions>
133+
<configuration>
134+
<products>
135+
<product>
136+
<id>stash</id>
137+
<instanceId>stash</instanceId>
138+
<version>${stash.version}</version>
139+
<dataVersion>${stash.data.version}</dataVersion>
140+
</product>
141+
</products>
142+
</configuration>
143+
</plugin>
144+
<plugin>
145+
<artifactId>maven-compiler-plugin</artifactId>
146+
<version>${plugin.compiler.version}</version>
147+
<configuration>
148+
<source>1.6</source>
149+
<target>1.6</target>
150+
</configuration>
151+
</plugin>
152+
</plugins>
153+
</build>
154+
155+
<repositories>
156+
<repository>
157+
<id>atlassian-public</id>
158+
<url>https://m2proxy.atlassian.com/repository/public</url>
159+
<snapshots>
160+
<enabled>true</enabled>
161+
<updatePolicy>daily</updatePolicy>
162+
<checksumPolicy>warn</checksumPolicy>
163+
</snapshots>
164+
<releases>
165+
<enabled>true</enabled>
166+
<checksumPolicy>warn</checksumPolicy>
167+
</releases>
168+
</repository>
169+
</repositories>
170+
171+
<pluginRepositories>
172+
<pluginRepository>
173+
<id>atlassian-public</id>
174+
<url>https://m2proxy.atlassian.com/repository/public</url>
175+
<releases>
176+
<enabled>true</enabled>
177+
<checksumPolicy>warn</checksumPolicy>
178+
</releases>
179+
<snapshots>
180+
<checksumPolicy>warn</checksumPolicy>
181+
</snapshots>
182+
</pluginRepository>
183+
</pluginRepositories>
184+
185+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package com.englishtown.stash.hook;
2+
3+
import com.atlassian.stash.hook.repository.AsyncPostReceiveRepositoryHook;
4+
import com.atlassian.stash.hook.repository.RepositoryHookContext;
5+
import com.atlassian.stash.i18n.I18nService;
6+
import com.atlassian.stash.internal.scm.git.GitCommandExitHandler;
7+
import com.atlassian.stash.repository.RefChange;
8+
import com.atlassian.stash.repository.Repository;
9+
import com.atlassian.stash.scm.CommandExitHandler;
10+
import com.atlassian.stash.scm.git.GitScm;
11+
import com.atlassian.stash.scm.git.GitScmCommandBuilder;
12+
import com.atlassian.stash.setting.RepositorySettingsValidator;
13+
import com.atlassian.stash.setting.Settings;
14+
import com.atlassian.stash.setting.SettingsValidationErrors;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
18+
import javax.annotation.Nonnull;
19+
import java.net.URI;
20+
import java.net.URISyntaxException;
21+
import java.util.Collection;
22+
23+
public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator {
24+
25+
static final String SETTING_MIRROR_REPO_URL = "mirrorRepoUrl";
26+
static final String SETTING_USERNAME = "username";
27+
static final String SETTING_PASSWORD = "password";
28+
29+
private final GitScm gitScm;
30+
private final I18nService i18nService;
31+
private static final Logger logger = LoggerFactory.getLogger(MirrorRepositoryHook.class);
32+
33+
public MirrorRepositoryHook(GitScm gitScm, I18nService i18nService) {
34+
this.gitScm = gitScm;
35+
this.i18nService = i18nService;
36+
}
37+
38+
/**
39+
* Calls the remote stash instance(s) to push the latest changes
40+
* <p/>
41+
* Callback method that is called just after a push is completed (or a pull request accepted).
42+
* This hook executes <i>after</i> the processing of a push and will not block the user client.
43+
* <p/>
44+
* Despite being asynchronous, the user who initiated this change is still available from
45+
*
46+
* @param context the context which the hook is being run with
47+
* @param refChanges the refs that have just been updated
48+
*/
49+
@Override
50+
public void postReceive(
51+
@Nonnull RepositoryHookContext context,
52+
@Nonnull Collection<RefChange> refChanges) {
53+
54+
try {
55+
logger.debug("MirrorRepositoryHook: postReceive started.");
56+
57+
Settings settings = context.getSettings();
58+
String mirrorRepoUrl = settings.getString(SETTING_MIRROR_REPO_URL);
59+
String username = settings.getString(SETTING_USERNAME);
60+
String password = settings.getString(SETTING_PASSWORD);
61+
62+
URI authenticatedUrl = getAuthenticatedUrl(mirrorRepoUrl, username, password);
63+
GitScmCommandBuilder builder = gitScm.getCommandBuilderFactory().builder(context.getRepository());
64+
CommandExitHandler exitHandler = new GitCommandExitHandler(i18nService, context.getRepository());
65+
PasswordHandler passwordHandler = new PasswordHandler(password, exitHandler);
66+
67+
// Call push command with the mirror flag set
68+
String result = builder
69+
.command("push")
70+
.argument("--mirror")
71+
.argument(authenticatedUrl.toString())
72+
.errorHandler(passwordHandler)
73+
.exitHandler(passwordHandler)
74+
.build(passwordHandler)
75+
.call();
76+
77+
builder.defaultExitHandler();
78+
logger.debug("MirrorRepositoryHook: postReceive completed with result '{}'.", result);
79+
80+
} catch (Exception e) {
81+
logger.error("MirrorRepositoryHook: Error running mirror hook", e);
82+
}
83+
84+
}
85+
86+
URI getAuthenticatedUrl(String mirrorRepoUrl, String username, String password) throws URISyntaxException {
87+
88+
URI uri = URI.create(mirrorRepoUrl);
89+
String userInfo = username + ":" + password;
90+
91+
return new URI(uri.getScheme(), userInfo, uri.getHost(), uri.getPort(),
92+
uri.getPath(), uri.getQuery(), uri.getFragment());
93+
94+
}
95+
96+
/**
97+
* Validate the given {@code settings} before they are persisted.
98+
*
99+
* @param settings to be validated
100+
* @param errors callback for reporting validation errors.
101+
* @param repository the context {@code Repository} the settings will be associated with
102+
*/
103+
@Override
104+
public void validate(
105+
@Nonnull Settings settings,
106+
@Nonnull SettingsValidationErrors errors,
107+
@Nonnull Repository repository) {
108+
109+
try {
110+
int count = 0;
111+
logger.debug("MirrorRepositoryHook: validate started.");
112+
113+
String mirrorRepoUrl = settings.getString(SETTING_MIRROR_REPO_URL, "");
114+
if (mirrorRepoUrl.isEmpty()) {
115+
count++;
116+
errors.addFieldError(SETTING_MIRROR_REPO_URL, "The mirror repo url is required.");
117+
} else {
118+
URI uri;
119+
try {
120+
uri = URI.create(mirrorRepoUrl);
121+
if (!uri.getScheme().toLowerCase().startsWith("http") || mirrorRepoUrl.contains("@")) {
122+
count++;
123+
errors.addFieldError(SETTING_MIRROR_REPO_URL, "The mirror repo url must be a valid http(s) " +
124+
"URI and the user should be specified separately.");
125+
}
126+
} catch (Exception ex) {
127+
count++;
128+
errors.addFieldError(SETTING_MIRROR_REPO_URL, "The mirror repo url must be a valid http(s) URI.");
129+
}
130+
}
131+
132+
if (settings.getString(SETTING_USERNAME, "").isEmpty()) {
133+
count++;
134+
errors.addFieldError(SETTING_USERNAME, "The username is required.");
135+
}
136+
137+
if (settings.getString(SETTING_PASSWORD, "").isEmpty()) {
138+
count++;
139+
errors.addFieldError(SETTING_PASSWORD, "The password is required.");
140+
}
141+
142+
logger.debug("MirrorRepositoryHook: validate completed with {} error(s).", count);
143+
144+
} catch (Exception e) {
145+
logger.error("Error running MirrorRepositoryHook validate.", e);
146+
errors.addFormError(e.getMessage());
147+
}
148+
149+
}
150+
151+
}

0 commit comments

Comments
 (0)