mirror of
https://github.com/imgfloat/server.git
synced 2026-03-22 23:10:38 +00:00
Add GitInfoService
This commit is contained in:
@@ -1,12 +1,10 @@
|
|||||||
package dev.kruhlmann.imgfloat.service;
|
package dev.kruhlmann.imgfloat.service;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import dev.kruhlmann.imgfloat.service.git.GitCommitInfo;
|
||||||
import java.io.IOException;
|
import dev.kruhlmann.imgfloat.service.git.GitCommitInfoSource;
|
||||||
import java.io.InputStream;
|
import java.util.List;
|
||||||
import java.io.InputStreamReader;
|
import java.util.Objects;
|
||||||
import java.util.Properties;
|
import java.util.Optional;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -15,18 +13,16 @@ import org.springframework.util.StringUtils;
|
|||||||
public class GitInfoService {
|
public class GitInfoService {
|
||||||
|
|
||||||
private static final String FALLBACK_GIT_SHA = "unknown";
|
private static final String FALLBACK_GIT_SHA = "unknown";
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GitInfoService.class);
|
|
||||||
|
|
||||||
private final String commitSha;
|
private final String commitSha;
|
||||||
private final String shortCommitSha;
|
private final String shortCommitSha;
|
||||||
private final String commitUrlPrefix;
|
private final String commitUrlPrefix;
|
||||||
|
|
||||||
public GitInfoService(@Value("${IMGFLOAT_COMMIT_URL_PREFIX:}") String commitUrlPrefix) {
|
public GitInfoService(
|
||||||
CommitInfo commitInfo = resolveFromGitProperties();
|
@Value("${IMGFLOAT_COMMIT_URL_PREFIX:}") String commitUrlPrefix,
|
||||||
if (commitInfo == null) {
|
List<GitCommitInfoSource> commitInfoSources
|
||||||
commitInfo = resolveFromGitBinary();
|
) {
|
||||||
}
|
GitCommitInfo commitInfo = resolveCommitInfo(commitInfoSources);
|
||||||
|
|
||||||
String full = commitInfo != null ? commitInfo.fullSha() : null;
|
String full = commitInfo != null ? commitInfo.fullSha() : null;
|
||||||
String abbreviated = commitInfo != null ? commitInfo.shortSha() : null;
|
String abbreviated = commitInfo != null ? commitInfo.shortSha() : null;
|
||||||
|
|
||||||
@@ -59,54 +55,20 @@ public class GitInfoService {
|
|||||||
return StringUtils.hasText(commitUrlPrefix);
|
return StringUtils.hasText(commitUrlPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommitInfo resolveFromGitProperties() {
|
private GitCommitInfo resolveCommitInfo(List<GitCommitInfoSource> commitInfoSources) {
|
||||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("git.properties")) {
|
if (commitInfoSources == null || commitInfoSources.isEmpty()) {
|
||||||
if (inputStream == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Properties properties = new Properties();
|
|
||||||
properties.load(inputStream);
|
|
||||||
String fullSha = normalize(properties.getProperty("git.commit.id"));
|
|
||||||
String shortSha = normalize(properties.getProperty("git.commit.id.abbrev"));
|
|
||||||
if (fullSha == null && shortSha == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new CommitInfo(fullSha, shortSha);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Unable to read git.properties from classpath", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CommitInfo resolveFromGitBinary() {
|
|
||||||
String fullSha = runGitCommand("rev-parse", "HEAD");
|
|
||||||
String shortSha = runGitCommand("rev-parse", "--short", "HEAD");
|
|
||||||
if (fullSha == null && shortSha == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new CommitInfo(fullSha, shortSha);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String runGitCommand(String... command) {
|
|
||||||
try {
|
|
||||||
Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
||||||
String output = reader.readLine();
|
|
||||||
int exitCode = process.waitFor();
|
|
||||||
if (exitCode == 0 && output != null && !output.isBlank()) {
|
|
||||||
return output.trim();
|
|
||||||
}
|
|
||||||
LOG.debug("Git command {} failed with exit code {}", String.join(" ", command), exitCode);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
LOG.debug("Thread interrupt during git command {}", String.join(" ", command), e);
|
|
||||||
return null;
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Git command IO error command {}", String.join(" ", command), e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Optional<GitCommitInfo> resolved = commitInfoSources
|
||||||
|
.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(GitCommitInfoSource::loadCommitInfo)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.map(this::normalizeCommitInfo)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst();
|
||||||
|
return resolved.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String abbreviate(String value) {
|
private String abbreviate(String value) {
|
||||||
@@ -130,5 +92,15 @@ public class GitInfoService {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CommitInfo(String fullSha, String shortSha) {}
|
private GitCommitInfo normalizeCommitInfo(GitCommitInfo commitInfo) {
|
||||||
|
if (commitInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String fullSha = normalize(commitInfo.fullSha());
|
||||||
|
String shortSha = normalize(commitInfo.shortSha());
|
||||||
|
if (fullSha == null && shortSha == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new GitCommitInfo(fullSha, shortSha);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package dev.kruhlmann.imgfloat.service.git;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(1)
|
||||||
|
public class GitBinaryCommitInfoSource implements GitCommitInfoSource {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GitBinaryCommitInfoSource.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<GitCommitInfo> loadCommitInfo() {
|
||||||
|
String fullSha = runGitCommand("rev-parse", "HEAD");
|
||||||
|
String shortSha = runGitCommand("rev-parse", "--short", "HEAD");
|
||||||
|
if (fullSha == null && shortSha == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new GitCommitInfo(fullSha, shortSha));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String runGitCommand(String... args) {
|
||||||
|
List<String> command = new ArrayList<>(args.length + 1);
|
||||||
|
command.add("git");
|
||||||
|
command.addAll(List.of(args));
|
||||||
|
try {
|
||||||
|
Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||||
|
String output = reader.readLine();
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
if (exitCode == 0 && output != null && !output.isBlank()) {
|
||||||
|
return output.trim();
|
||||||
|
}
|
||||||
|
LOG.debug("Git command {} failed with exit code {}", String.join(" ", command), exitCode);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
LOG.debug("Thread interrupt during git command {}", String.join(" ", command), e);
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("Git command IO error command {}", String.join(" ", command), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.kruhlmann.imgfloat.service.git;
|
||||||
|
|
||||||
|
public record GitCommitInfo(String fullSha, String shortSha) {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.kruhlmann.imgfloat.service.git;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface GitCommitInfoSource {
|
||||||
|
Optional<GitCommitInfo> loadCommitInfo();
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package dev.kruhlmann.imgfloat.service.git;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(0)
|
||||||
|
public class GitPropertiesCommitInfoSource implements GitCommitInfoSource {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GitPropertiesCommitInfoSource.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<GitCommitInfo> loadCommitInfo() {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("git.properties")) {
|
||||||
|
if (inputStream == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.load(inputStream);
|
||||||
|
String fullSha = properties.getProperty("git.commit.id");
|
||||||
|
String shortSha = properties.getProperty("git.commit.id.abbrev");
|
||||||
|
if (!StringUtils.hasText(fullSha) && !StringUtils.hasText(shortSha)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new GitCommitInfo(fullSha, shortSha));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Unable to read git.properties from classpath", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package dev.kruhlmann.imgfloat.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import dev.kruhlmann.imgfloat.service.git.GitCommitInfo;
|
||||||
|
import dev.kruhlmann.imgfloat.service.git.GitCommitInfoSource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class GitInfoServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesFirstCommitInfoSource() {
|
||||||
|
TrackingSource first = new TrackingSource(Optional.of(new GitCommitInfo("full-sha", "short")));
|
||||||
|
TrackingSource second = new TrackingSource(Optional.of(new GitCommitInfo("other-sha", "other")));
|
||||||
|
|
||||||
|
GitInfoService service = new GitInfoService("https://example/commit/", List.of(first, second));
|
||||||
|
|
||||||
|
assertThat(service.getShortCommitSha()).isEqualTo("short");
|
||||||
|
assertThat(service.getCommitUrl()).isEqualTo("https://example/commit/full-sha");
|
||||||
|
assertThat(first.calls()).isEqualTo(1);
|
||||||
|
assertThat(second.calls()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fallsBackToNextSourceWhenFirstIsEmpty() {
|
||||||
|
TrackingSource first = new TrackingSource(Optional.empty());
|
||||||
|
TrackingSource second = new TrackingSource(Optional.of(new GitCommitInfo(null, "abc1234")));
|
||||||
|
|
||||||
|
GitInfoService service = new GitInfoService("https://example/commit/", List.of(first, second));
|
||||||
|
|
||||||
|
assertThat(service.getShortCommitSha()).isEqualTo("abc1234");
|
||||||
|
assertThat(service.getCommitUrl()).isEqualTo("https://example/commit/abc1234");
|
||||||
|
assertThat(first.calls()).isEqualTo(1);
|
||||||
|
assertThat(second.calls()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void abbreviatesShortShaWhenOnlyFullIsProvided() {
|
||||||
|
GitInfoService service = new GitInfoService(
|
||||||
|
"https://example/commit/",
|
||||||
|
List.of(() -> Optional.of(new GitCommitInfo("1234567890", null)))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(service.getShortCommitSha()).isEqualTo("1234567");
|
||||||
|
assertThat(service.getCommitUrl()).isEqualTo("https://example/commit/1234567890");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hidesCommitUrlWhenPrefixOrShaIsMissing() {
|
||||||
|
GitInfoService missingPrefix = new GitInfoService(" ", List.of());
|
||||||
|
GitInfoService missingSha = new GitInfoService("https://example/commit/", List.of());
|
||||||
|
|
||||||
|
assertThat(missingPrefix.shouldShowCommitChip()).isFalse();
|
||||||
|
assertThat(missingPrefix.getCommitUrl()).isNull();
|
||||||
|
assertThat(missingSha.getShortCommitSha()).isEqualTo("unknown");
|
||||||
|
assertThat(missingSha.getCommitUrl()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TrackingSource implements GitCommitInfoSource {
|
||||||
|
private final Optional<GitCommitInfo> payload;
|
||||||
|
private final AtomicInteger calls = new AtomicInteger();
|
||||||
|
|
||||||
|
private TrackingSource(Optional<GitCommitInfo> payload) {
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<GitCommitInfo> loadCommitInfo() {
|
||||||
|
calls.incrementAndGet();
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calls() {
|
||||||
|
return calls.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user