Bitbucket中用于同步git仓库的插件,支持http(s)和ssh协议。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

147 lines
6.1 KiB

package com.englishtown.bitbucket.hook;
import com.atlassian.bitbucket.concurrent.BucketProcessor;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.scm.Command;
import com.atlassian.bitbucket.scm.ScmCommandBuilder;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.scm.git.command.GitCommandExitHandler;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.user.SecurityService;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.List;
import java.util.Locale;
import static com.englishtown.bitbucket.hook.MirrorRepositoryHook.PROP_PREFIX;
public class MirrorBucketProcessor implements BucketProcessor<MirrorRequest> {
static final String PROP_TIMEOUT = PROP_PREFIX + "timeout";
private static final String DEFAULT_REFSPEC = "+refs/heads/*:refs/heads/*";
private static final Logger log = LoggerFactory.getLogger(MirrorBucketProcessor.class);
private final I18nService i18nService;
private final PasswordEncryptor passwordEncryptor;
private final RepositoryService repositoryService;
private final ScmService scmService;
private final SecurityService securityService;
private final Duration timeout;
public MirrorBucketProcessor(I18nService i18nService, PasswordEncryptor passwordEncryptor,
ApplicationPropertiesService propertiesService, RepositoryService repositoryService,
ScmService scmService, SecurityService securityService) {
this.i18nService = i18nService;
this.passwordEncryptor = passwordEncryptor;
this.repositoryService = repositoryService;
this.scmService = scmService;
this.securityService = securityService;
timeout = Duration.ofSeconds(propertiesService.getPluginProperty(PROP_TIMEOUT, 120L));
}
@Override
public void process(@Nonnull String key, @Nonnull List<MirrorRequest> requests) {
if (requests.isEmpty()) {
return;
}
// Every request is for the same mirror URL, and the same repository ID. In case the
// settings (e.g. username/password) have been changed since the first request was
// queued, we process the _last_ request in the list. Since mirroring pushes all of
// the configured refspecs, any single request should roll up changes from any number
// of requests
MirrorRequest request = requests.get(requests.size() - 1);
securityService.withPermission(Permission.REPO_READ, "Mirror changes")
.call(() -> {
Repository repository = repositoryService.getById(request.getRepositoryId());
if (repository == null) {
log.debug("{}: Repository has been deleted", request.getRepositoryId());
return null;
}
if (repositoryService.isEmpty(repository)) {
log.debug("{}: The repository is empty", repository);
return null;
}
runMirrorCommand(request.getSettings(), repository);
return null;
});
}
private void runMirrorCommand(MirrorSettings settings, Repository repository) {
log.debug("{}: Preparing to push changes to mirror", repository);
String password = passwordEncryptor.decrypt(settings.password);
String authenticatedUrl = getAuthenticatedUrl(settings.mirrorRepoUrl, settings.username, password);
// Call push command with the prune flag and refspecs for heads and tags
// Do not use the mirror flag as pull-request refs are included
ScmCommandBuilder<?> builder = scmService.createBuilder(repository)
.command("push")
.argument("--prune") // this deletes locally deleted branches
.argument(authenticatedUrl)
.argument("--force");
// Use an atomic transaction to have a consistent state
if (settings.atomic) {
builder.argument("--atomic");
}
// Add refspec args
String refspecs = Strings.isNullOrEmpty(settings.refspec) ? DEFAULT_REFSPEC : settings.refspec;
for (String refspec : refspecs.split("\\s|\\n")) {
if (!Strings.isNullOrEmpty(refspec)) {
builder.argument(refspec);
}
}
// Add tags refspec
if (settings.tags) {
builder.argument("+refs/tags/*:refs/tags/*");
}
// Add notes refspec
if (settings.notes) {
builder.argument("+refs/notes/*:refs/notes/*");
}
PasswordHandler passwordHandler = new PasswordHandler(settings.password,
new GitCommandExitHandler(i18nService, repository));
Command<String> command = builder.errorHandler(passwordHandler)
.exitHandler(passwordHandler)
.build(passwordHandler);
command.setTimeout(timeout);
Object result = command.call();
log.info("{}: Push completed with the following output:\n{}", repository, result);
}
String getAuthenticatedUrl(String mirrorRepoUrl, String username, String password) {
// Only http(s) has username/password
if (!mirrorRepoUrl.toLowerCase(Locale.ROOT).startsWith("http")) {
return mirrorRepoUrl;
}
URI uri = URI.create(mirrorRepoUrl);
String userInfo = username + ":" + password;
try {
return new URI(uri.getScheme(), userInfo, uri.getHost(), uri.getPort(),
uri.getPath(), uri.getQuery(), uri.getFragment()).toString();
} catch (URISyntaxException e) {
throw new IllegalStateException("The configured mirror URL (" + mirrorRepoUrl + ") is invalid", e);
}
}
}