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;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Properties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import dev.kruhlmann.imgfloat.service.git.GitCommitInfo;
|
||||
import dev.kruhlmann.imgfloat.service.git.GitCommitInfoSource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -15,18 +13,16 @@ import org.springframework.util.StringUtils;
|
||||
public class GitInfoService {
|
||||
|
||||
private static final String FALLBACK_GIT_SHA = "unknown";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitInfoService.class);
|
||||
|
||||
private final String commitSha;
|
||||
private final String shortCommitSha;
|
||||
private final String commitUrlPrefix;
|
||||
|
||||
public GitInfoService(@Value("${IMGFLOAT_COMMIT_URL_PREFIX:}") String commitUrlPrefix) {
|
||||
CommitInfo commitInfo = resolveFromGitProperties();
|
||||
if (commitInfo == null) {
|
||||
commitInfo = resolveFromGitBinary();
|
||||
}
|
||||
|
||||
public GitInfoService(
|
||||
@Value("${IMGFLOAT_COMMIT_URL_PREFIX:}") String commitUrlPrefix,
|
||||
List<GitCommitInfoSource> commitInfoSources
|
||||
) {
|
||||
GitCommitInfo commitInfo = resolveCommitInfo(commitInfoSources);
|
||||
String full = commitInfo != null ? commitInfo.fullSha() : null;
|
||||
String abbreviated = commitInfo != null ? commitInfo.shortSha() : null;
|
||||
|
||||
@@ -59,54 +55,20 @@ public class GitInfoService {
|
||||
return StringUtils.hasText(commitUrlPrefix);
|
||||
}
|
||||
|
||||
private CommitInfo resolveFromGitProperties() {
|
||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("git.properties")) {
|
||||
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);
|
||||
private GitCommitInfo resolveCommitInfo(List<GitCommitInfoSource> commitInfoSources) {
|
||||
if (commitInfoSources == null || commitInfoSources.isEmpty()) {
|
||||
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) {
|
||||
@@ -130,5 +92,15 @@ public class GitInfoService {
|
||||
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