Added file changes.
This commit is contained in:
parent
7aa45c4635
commit
d16d81209d
|
@ -0,0 +1,38 @@
|
|||
<?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/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>nl.andrewl</groupId>
|
||||
<artifactId>distribugit</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>6.1.0.202203080745-r</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
|
||||
<version>6.1.0.202203080745-r</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.36</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,191 @@
|
|||
package nl.andrewl.distribugit;
|
||||
|
||||
import org.eclipse.jgit.api.CloneCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DistribuGit {
|
||||
private final RepositorySelector selector;
|
||||
private final RepositoryAction action;
|
||||
private final GitCredentials credentials;
|
||||
private final StatusListener statusListener;
|
||||
private final Path workingDir;
|
||||
private final boolean strictFail;
|
||||
private final boolean cleanup;
|
||||
|
||||
private int stepsComplete;
|
||||
private int stepsTotal;
|
||||
|
||||
public DistribuGit(
|
||||
RepositorySelector selector,
|
||||
RepositoryAction action,
|
||||
GitCredentials credentials,
|
||||
StatusListener statusListener,
|
||||
Path workingDir,
|
||||
boolean strictFail,
|
||||
boolean cleanup
|
||||
) {
|
||||
this.selector = selector;
|
||||
this.action = action;
|
||||
this.credentials = credentials;
|
||||
this.statusListener = statusListener;
|
||||
this.workingDir = workingDir;
|
||||
this.strictFail = strictFail;
|
||||
this.cleanup = cleanup;
|
||||
}
|
||||
|
||||
public void doActions() throws IOException {
|
||||
stepsComplete = 0;
|
||||
Utils.delete(workingDir); // Delete the directory if it already exists.
|
||||
Files.createDirectory(workingDir);
|
||||
statusListener.messageReceived("Prepared temporary directory for repositories.");
|
||||
try {
|
||||
List<String> repositoryURIs = selector.getURIs();
|
||||
stepsTotal = 2 * repositoryURIs.size();
|
||||
Map<String, Path> repoDirs = downloadRepositories(repositoryURIs);
|
||||
applyActionToRepositories(repoDirs);
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
if (cleanup) {
|
||||
statusListener.messageReceived("Removing all repositories.");
|
||||
Utils.delete(workingDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void completeStep() {
|
||||
stepsComplete++;
|
||||
statusListener.progressUpdated(stepsComplete / (float) stepsTotal);
|
||||
}
|
||||
|
||||
private Map<String, Path> downloadRepositories(List<String> uris) throws IOException {
|
||||
Map<String, Path> repositoryDirs = new HashMap<>();
|
||||
int dirIdx = 1;
|
||||
for (String repositoryURI : uris) {
|
||||
Path repoDir = workingDir.resolve(Integer.toString(dirIdx++));
|
||||
try {
|
||||
statusListener.messageReceived("Cloning repository " + repositoryURI + " to " + repoDir);
|
||||
CloneCommand clone = Git.cloneRepository();
|
||||
credentials.addCredentials(clone);
|
||||
clone.setDirectory(repoDir.toFile());
|
||||
clone.setURI(repositoryURI);
|
||||
try (var ignored = clone.call()) {
|
||||
repositoryDirs.put(repositoryURI, repoDir);
|
||||
} catch (Exception e) {
|
||||
if (strictFail) {
|
||||
throw new IOException(e);
|
||||
} else {
|
||||
repositoryDirs.put(repositoryURI, null);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (strictFail) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
completeStep();
|
||||
}
|
||||
return repositoryDirs;
|
||||
}
|
||||
|
||||
private void applyActionToRepositories(Map<String, Path> repoDirs) throws IOException {
|
||||
for (var entry : repoDirs.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
try (Git git = Git.open(entry.getValue().toFile())) {
|
||||
statusListener.messageReceived("Applying action to repository " + entry.getKey());
|
||||
action.doAction(git);
|
||||
} catch (Exception e) {
|
||||
if (strictFail) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
statusListener.messageReceived("Skipping action on repository " + entry.getKey() + " because it could not be downloaded.");
|
||||
}
|
||||
completeStep();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private RepositorySelector selector;
|
||||
private RepositoryAction action;
|
||||
private GitCredentials credentials = cmd -> {};
|
||||
private StatusListener statusListener;
|
||||
private Path workingDir = Path.of(".", ".distribugit_tmp");
|
||||
private boolean strictFail = true;
|
||||
private boolean cleanup = false;
|
||||
|
||||
public Builder selector(RepositorySelector selector) {
|
||||
this.selector = selector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder action(RepositoryAction action) {
|
||||
this.action = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder credentials(GitCredentials credentials) {
|
||||
this.credentials = credentials;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder statusListener(StatusListener listener) {
|
||||
this.statusListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder workingDir(Path dir) {
|
||||
this.workingDir = dir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder strictFail(boolean strictFail) {
|
||||
this.strictFail = strictFail;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cleanup(boolean cleanup) {
|
||||
this.cleanup = cleanup;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DistribuGit build() {
|
||||
return new DistribuGit(selector, action, credentials, statusListener, workingDir, strictFail, cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
new Builder()
|
||||
.selector(RepositorySelector.from(
|
||||
"https://github.com/andrewlalis/RandomHotbar.git",
|
||||
"https://github.com/andrewlalis/CoyoteCredit.git",
|
||||
"https://github.com/andrewlalis/SignalsAndSystems2021.git"
|
||||
))
|
||||
.action(git -> System.out.println("Cloned!"))
|
||||
.statusListener(new StatusListener() {
|
||||
@Override
|
||||
public void progressUpdated(float percentage) {
|
||||
System.out.printf("Progress: %.1f%%%n", percentage * 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(String message) {
|
||||
System.out.println("Message: " + message);
|
||||
}
|
||||
})
|
||||
.strictFail(true)
|
||||
.cleanup(true)
|
||||
.build().doActions();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package nl.andrewl.distribugit;
|
||||
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import org.eclipse.jgit.api.TransportCommand;
|
||||
import org.eclipse.jgit.transport.SshSessionFactory;
|
||||
import org.eclipse.jgit.transport.SshTransport;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory;
|
||||
import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface GitCredentials {
|
||||
void addCredentials(TransportCommand<?, ?> gitCommand) throws Exception;
|
||||
|
||||
static GitCredentials ofUsernamePassword(String username, String password) {
|
||||
return cmd -> cmd.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));
|
||||
}
|
||||
|
||||
static GitCredentials ofSshKey() {
|
||||
return ofSshKey(Path.of(System.getProperty("user.home"), ".ssh", "id_rsa"));
|
||||
}
|
||||
|
||||
static GitCredentials ofSshKey(Path privateKeyFile) {
|
||||
return ofSshKey(
|
||||
privateKeyFile,
|
||||
privateKeyFile.getParent().resolve(privateKeyFile.getFileName().toString() + ".pub"),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
static GitCredentials ofSshKey(Path privateKeyFile, Path publicKeyFile, String passphrase) {
|
||||
System.out.println("Using private key at " + privateKeyFile.toAbsolutePath());
|
||||
SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
|
||||
@Override
|
||||
protected void configure(OpenSshConfig.Host hc, Session session) {
|
||||
session.setConfig("StrictHostKeyChecking", "no"); // Don't require the host to be in the system's known hosts list.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JSch createDefaultJSch(FS fs) throws JSchException {
|
||||
var jsch = super.createDefaultJSch(fs);
|
||||
jsch.removeAllIdentity();
|
||||
jsch.addIdentity(
|
||||
privateKeyFile.toAbsolutePath().toString(),
|
||||
publicKeyFile.toAbsolutePath().toString(),
|
||||
passphrase == null ? null : passphrase.getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
return jsch;
|
||||
}
|
||||
};
|
||||
return cmd -> cmd.setTransportConfigCallback(transport -> {
|
||||
if (transport instanceof SshTransport sshTransport) {
|
||||
sshTransport.setSshSessionFactory(sshSessionFactory);
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid git transport method: " + transport.getClass().getSimpleName() + "; Cannot apply SSH session factory to this type of transport.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package nl.andrewl.distribugit;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
|
||||
public interface RepositoryAction {
|
||||
void doAction(Git git) throws Exception;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package nl.andrewl.distribugit;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Component which produces a list of repositories to operate on.
|
||||
*/
|
||||
public interface RepositorySelector {
|
||||
List<String> getURIs() throws Exception;
|
||||
|
||||
static RepositorySelector fromCollection(Collection<String> uris) {
|
||||
return () -> new ArrayList<>(uris);
|
||||
}
|
||||
|
||||
static RepositorySelector from(String... uris) {
|
||||
return () -> List.of(uris);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package nl.andrewl.distribugit;
|
||||
|
||||
public interface StatusListener {
|
||||
void progressUpdated(float percentage);
|
||||
void messageReceived(String message);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package nl.andrewl.distribugit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public class Utils {
|
||||
public static void deleteNoThrow(Path path) {
|
||||
try {
|
||||
delete(path);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(Path path) throws IOException {
|
||||
if (path == null) return;
|
||||
if (!Files.exists(path)) return;
|
||||
if (Files.isRegularFile(path)) {
|
||||
Files.delete(path);
|
||||
} else {
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue