diff --git a/core/pom.xml b/core/pom.xml index 910663b8..defc2fbd 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -94,6 +94,11 @@ com.google.guava guava + + + org.eclipse.jgit + org.eclipse.jgit + diff --git a/core/src/main/java/cz/xtf/core/git/GitResolver.java b/core/src/main/java/cz/xtf/core/git/GitResolver.java new file mode 100644 index 00000000..a2c6018a --- /dev/null +++ b/core/src/main/java/cz/xtf/core/git/GitResolver.java @@ -0,0 +1,7 @@ +package cz.xtf.core.git; + +interface GitResolver { + String resolveRepoUrl(); + + String resolveRepoRef(); +} diff --git a/core/src/main/java/cz/xtf/core/git/GitResolverFactory.java b/core/src/main/java/cz/xtf/core/git/GitResolverFactory.java new file mode 100644 index 00000000..4ffedf72 --- /dev/null +++ b/core/src/main/java/cz/xtf/core/git/GitResolverFactory.java @@ -0,0 +1,16 @@ +package cz.xtf.core.git; + +import cz.xtf.core.config.XTFConfig; + +class GitResolverFactory { + static final String GIT_URL = "xtf.git.repository.url"; + static final String GIT_BRANCH = "xtf.git.repository.ref"; + + public static GitResolver createResolver() { + if (XTFConfig.get(GIT_URL) == null || XTFConfig.get(GIT_BRANCH) == null) { + return new JGitResolver(); + } else { + return new SystemPropertyGitResolver(); + } + } +} diff --git a/core/src/main/java/cz/xtf/core/git/GitUtils.java b/core/src/main/java/cz/xtf/core/git/GitUtils.java new file mode 100644 index 00000000..65e45673 --- /dev/null +++ b/core/src/main/java/cz/xtf/core/git/GitUtils.java @@ -0,0 +1,14 @@ +package cz.xtf.core.git; + +public class GitUtils { + private static final GitResolver gitResolver = GitResolverFactory.createResolver(); + + // Static method to get repo URL and ref + public static String getRepoUrl() { + return gitResolver.resolveRepoUrl(); + } + + public static String getRepoRef() { + return gitResolver.resolveRepoRef(); + } +} diff --git a/core/src/main/java/cz/xtf/core/git/JGitResolver.java b/core/src/main/java/cz/xtf/core/git/JGitResolver.java new file mode 100644 index 00000000..fd02a380 --- /dev/null +++ b/core/src/main/java/cz/xtf/core/git/JGitResolver.java @@ -0,0 +1,140 @@ +package cz.xtf.core.git; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.RemoteConfig; + +import lombok.extern.slf4j.Slf4j; + +/** + * Try to resolve repository remote URL and branch from .git directory + *

+ * This method tries to match HEAD commit to remote references. + * If there is a match, the remote URL and branch are set. + * For attached HEAD this method could be simplified with jgit methods, but + * "universal" approach was chosen, as for example Jenkins git plugin creates a detached state. + * In case of multiple matches, upstream or origin (in this order) is preferred. + *

+ */ +@Slf4j +class JGitResolver implements GitResolver { + private static final String URL_TEMPLATE = "https://%s/%s/%s"; + private static String reference; + private static String url; + + /** + * Try to set repository ref and URL from HEAD commit + */ + public JGitResolver() { + try { + resolveRepoFromHEAD(); + } catch (IOException | URISyntaxException e) { + log.error("Failed to resolve repository from HEAD", e); + throw new RuntimeException("Failed to resolve repository from HEAD with error: " + e.getMessage()); + } + } + + /** + * Try to set repository ref and URL from HEAD commit + */ + private static void resolveRepoFromHEAD() throws IOException, URISyntaxException { + //look for a git repository recursively till system root folder + Repository repository = new FileRepositoryBuilder().findGitDir().build(); + + if (repository == null) { + log.error("Failed to find a git repository"); + return; + } + + //get current commit hash + ObjectId commitId = repository.resolve("HEAD"); + + //get all remote references + List refs = repository.getRefDatabase().getRefs().stream() + .filter(reference -> reference.getName().startsWith("refs/remotes/")).collect(Collectors.toList()); + + List matches = new ArrayList<>(); + // Walk through all the refs to see if any point to this commit + for (Ref ref : refs) { + if (ref.getObjectId().equals(commitId)) { + matches.add(ref.getName()); + } + } + + if (matches.isEmpty()) { + log.error("No remote references found for the current commit"); + return; + } + + //In case there are multiple matches, we prefer upstream or origin (in this order) + List preferredMatches = matches.stream() + .filter(reference -> reference.contains("upstream") || reference.contains("origin")) + .sorted(Comparator.reverseOrder()) // 1) upstream 2) origin + .collect(Collectors.toList()); + + if (matches.size() > 1 && !preferredMatches.isEmpty()) { + matches = preferredMatches; + } + + //branch is string behind the last / + reference = matches.stream().findFirst().map(ref -> ref.substring(ref.lastIndexOf('/') + 1)).orElse(null); + + log.info("xtf.git.repository.ref got automatically resolved as {}", reference); + + String remote = repository.getRemoteName(matches.get(0)); + url = getRemoteUrl(repository, remote); + + if (url != null) { + log.info("xtf.git.repository.url got automatically resolved as {}", url); + } + + } + + /** + * given a remote reference, get it's remote URL + * + * @param repository git repository + * @param remoteReference reference in format "refs/remotes/remote/branch" + * @return URL in HTTPS format + */ + private static String getRemoteUrl(Repository repository, String remoteReference) throws URISyntaxException { + RemoteConfig remoteConfig = new RemoteConfig(repository.getConfig(), remoteReference); + if (remoteConfig.getURIs() == null || remoteConfig.getURIs().isEmpty()) { + log.info("Missing URI in git remote ref '{}'", remoteReference); + return null; + } + // we expect a single URI + String[] pathTokens = remoteConfig.getURIs().get(0).getPath().split("/"); + if (pathTokens.length != 2) { + log.info("Unexpected path '{}' in URI '{}' of git remote ref '{}'", remoteConfig.getURIs().get(0).getPath(), + remoteConfig.getURIs().get(0), remoteReference); + return null; + } + // the URI must be in HTTPS format + return getRepositoryUrl(remoteConfig.getURIs().get(0).getHost(), pathTokens[0], pathTokens[1]); + } + + /** + * We require HTTPS format, for unauthorized access to the repository, let's convert it + */ + private static String getRepositoryUrl(String host, String remote, String repository) { + return String.format(URL_TEMPLATE, host, remote, repository); + } + + public String resolveRepoUrl() { + return url; + } + + public String resolveRepoRef() { + return reference; + } +} diff --git a/core/src/main/java/cz/xtf/core/git/SystemPropertyGitResolver.java b/core/src/main/java/cz/xtf/core/git/SystemPropertyGitResolver.java new file mode 100644 index 00000000..d75134a3 --- /dev/null +++ b/core/src/main/java/cz/xtf/core/git/SystemPropertyGitResolver.java @@ -0,0 +1,16 @@ +package cz.xtf.core.git; + +import cz.xtf.core.config.XTFConfig; + +class SystemPropertyGitResolver implements GitResolver { + static final String GIT_URL = "xtf.git.repository.url"; + static final String GIT_BRANCH = "xtf.git.repository.ref"; + + public String resolveRepoUrl() { + return XTFConfig.get(GIT_URL); + } + + public String resolveRepoRef() { + return XTFConfig.get(GIT_BRANCH); + } +} diff --git a/pom.xml b/pom.xml index 1562765a..aa91d41e 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,7 @@ 1.18.22 2.8.9 32.0.1-jre + 5.13.3.202401111512-r 3.2.1 @@ -226,6 +227,12 @@ openshift-server-mock ${version.openshift-client} + + + org.eclipse.jgit + org.eclipse.jgit + ${version.org.eclipse.jgit} +