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