Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: jgit status #109

Open
wants to merge 5 commits into
base: integrate-jgit
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@

name: Java CI with Maven

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
on: [push, pull_request]

jobs:
build:
Expand All @@ -38,8 +34,7 @@ jobs:
native-image-job-reports: 'true'
- name: Build executable file
run: |
native-image -march=compatibility -cp target/gis-*.jar "org.nqm.Gis" --no-fallback --initialize-at-run-time=io.netty.handler.ssl.BouncyCastleAlpnSslUtils
mv org.nqm.gis gis
docker build -f Containerfile -t gis . || return 1; docker create --name dkgis_ gis:latest; docker cp dkgis_:/app/gis/gis .; docker rm -f dkgis_
./gis --version
du -sh gis
- name: Upload artifact
Expand Down
50 changes: 38 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<jar.plugin.version>3.3.0</jar.plugin.version>
<jacoco.version>0.8.12</jacoco.version>
<commit.id.version>9.0.1</commit.id.version>
<jgit.version>6.10.0.202406032230-r</jgit.version>
</properties>

<dependencies>
Expand All @@ -32,6 +33,23 @@
<artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>${jgit.version}</version>
</dependency>

<!-- these 2 slf4j deps are required for jgit -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down Expand Up @@ -64,18 +82,6 @@
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -180,6 +186,26 @@
</formats>
</configuration>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/org/nqm/Gis.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.nqm;

import org.nqm.command.GisCommand;
import org.nqm.command.GisVersion;
import org.nqm.command.GitCommand;
import org.nqm.config.GisLog;
import org.nqm.utils.GisProcessUtils;
import picocli.CommandLine;
Expand All @@ -16,7 +16,7 @@
description = "Git extension wrapper which supports submodules",
mixinStandardHelpOptions = true,
versionProvider = GisVersion.class)
public class Gis extends GitCommand {
public class Gis extends GisCommand {

private static final IExecutionExceptionHandler GLOBAL_EXCEPTION_HANLER = new IExecutionExceptionHandler() {
@Override
Expand All @@ -38,9 +38,14 @@ public static void setDryRun(boolean dryRun) {
}

public static void main(String... args) {
// try {
// System.out.println(GitStatusCommand.status(Path.of("/home/minh/projects/test/small-git-root-module/small-git-domain/.git"), false));
// } catch (NoWorkTreeException | IOException | GitAPIException e) {
// e.printStackTrace();
// }

var gis = new CommandLine(new Gis());
gis.setExecutionExceptionHandler(GLOBAL_EXCEPTION_HANLER);

gis.execute(args.length == 0
? new String[] {GIT_STATUS, "--one-line"}
: args);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/nqm/command/CommandVerticle.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.nqm.command;

import static org.nqm.command.GitCommand.HOOKS_OPTION;
import static org.nqm.command.GisCommand.HOOKS_OPTION;
import static org.nqm.utils.GisStringUtils.isNotBlank;
import static org.nqm.utils.StdOutUtils.gitStatus;
import static org.nqm.utils.StdOutUtils.gitStatusOneLine;
Expand Down Expand Up @@ -95,7 +95,7 @@ private static void safelyPrint(
var sb = new StringBuilder(infof("%s", "" + path.getFileName()));
var isOneLineOpt = Stream.of(gisOptions).anyMatch("--gis-one-line"::equals);
while (isNotBlank(line = input.readLine())) {
if (commandWithArgs[1].equals(GitCommand.GIT_STATUS)) {
if (commandWithArgs[1].equals(GisCommand.GIT_STATUS)) {
sb.append(isOneLineOpt ? gitStatusOneLine(line) : gitStatus(line));
} else {
sb.append("%n %s".formatted(line));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.nqm.command;

import static org.nqm.command.Wrapper.forEachModuleDo;
import static org.nqm.command.Wrapper.forEachModuleStatus;
import static org.nqm.command.Wrapper.forEachModuleWith;
import static org.nqm.config.GisConfig.currentDir;
import java.io.BufferedReader;
Expand All @@ -15,9 +16,13 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.nqm.GisException;
import org.nqm.config.GisConfig;
import org.nqm.config.GisLog;
Expand All @@ -28,7 +33,7 @@
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

public class GitCommand {
public class GisCommand {

private static final String CHECKOUT = "checkout";
private static final String FETCHED_AT = "(fetched at: %s)";
Expand All @@ -47,12 +52,18 @@ void pull() throws IOException {
}

@Command(name = GIT_STATUS, aliases = "st", description = "Show the working trees status")
void status(@Option(names = "--one-line") boolean oneLineOpt) throws IOException {
if (oneLineOpt) {
forEachModuleDo(GIT_STATUS, "-sb", "--ignore-submodules", "--porcelain=v2", "--gis-one-line");
} else {
forEachModuleDo(GIT_STATUS, "-sb", "--ignore-submodules", "--porcelain=v2");
}
void status(@Option(names = "--one-line") boolean oneLiner) throws IOException {
var result = new ConcurrentLinkedQueue<String>();
forEachModuleStatus(f -> {
try {
result.add(GitStatusCommand.status(f, oneLiner));
} catch (IOException | GitAPIException e) {
GisLog.debug(e);
}
});
StdOutUtils.println(String.join(
GisStringUtils.NEWLINE,
result.stream().collect(Collectors.toCollection(TreeSet::new))));
if (Files.exists(TMP_FILE)) {
var lastFetched = Files.readString(TMP_FILE);
if (GisStringUtils.isNotBlank(lastFetched)) {
Expand Down Expand Up @@ -235,7 +246,7 @@ void generateCompletion(
try (var out = new FileOutputStream(file.toFile())) {
while ((line = buffer.readLine()) != null) {
out.write(line.getBytes());
out.write("%n".formatted().getBytes());
out.write(GisStringUtils.NEWLINE.getBytes());
}
}
return;
Expand Down
138 changes: 138 additions & 0 deletions src/main/java/org/nqm/command/GitStatusCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.nqm.command;

import static org.nqm.model.FileStatus.ADDED;
import static org.nqm.model.FileStatus.CHANGED;
import static org.nqm.model.FileStatus.CONFLICT;
import static org.nqm.model.FileStatus.MISSING;
import static org.nqm.model.FileStatus.MODIFIED;
import static org.nqm.model.FileStatus.REMOVED;
import static org.nqm.model.FileStatus.UNTRACKED;
import static org.nqm.model.FileStatus.UNTRACKED_DIRS;
import static org.nqm.utils.GisStringUtils.getDirectoryName;
import static org.nqm.utils.StdOutUtils.CL_GREEN;
import static org.nqm.utils.StdOutUtils.CL_RED;
import static org.nqm.utils.StdOutUtils.coloringBranch;
import static org.nqm.utils.StdOutUtils.coloringWord;
import static org.nqm.utils.StdOutUtils.infof;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.BranchConfig;
import org.eclipse.jgit.lib.BranchTrackingStatus;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
import org.nqm.model.FileStatus;
import org.nqm.utils.GisStringUtils;
import org.nqm.utils.StdOutUtils;

public class GitStatusCommand {

private static Repository openRepo(Path f) throws IOException {
var repo = new FileRepositoryBuilder().readEnvironment();
if (f != null) {
repo.findGitDir(f.toFile());
} else {
repo.findGitDir();
}
return repo.build();
}

private static String aheadBehind(Repository repo) throws IOException {
var trackingStatus = BranchTrackingStatus.of(repo, repo.getBranch());
int ahead = trackingStatus.getAheadCount();
int behind = trackingStatus.getBehindCount();
return StdOutUtils.buildAheadBehind(ahead, behind);
}

private static String statusOneLiner(Repository repo, Status status, String localBranch)
throws IOException {
// TODO: add styling modified and changed files
// TODO: add styling for upstream?
var result = new StringBuilder(" ")
.append(coloringBranch(localBranch))
.append(aheadBehind(repo));

var updatedFiles = Stream.of(status.getModified().stream(),
status.getChanged().stream(),
status.getAdded().stream(),
status.getMissing().stream(),
status.getRemoved().stream(),
status.getUntracked().stream(),
status.getConflicting().stream(),
status.getUntrackedFolders().stream())
.reduce(Stream::concat)
.orElseGet(Stream::empty)
.distinct()
.collect(Collectors.joining(" "));
if (GisStringUtils.isNotBlank(updatedFiles)) {
result.append(" ").append(updatedFiles);
}
return result.toString();
}

private static String statusFull(Repository repo, Status status, String localBranch) throws IOException {
var trackingBranch = new BranchConfig(repo.getConfig(), localBranch)
.getTrackingBranch()
.replace("refs/remotes/", "");
var result = new StringBuilder()
.append(GisStringUtils.NEWLINE)
.append(" ## ")
.append(coloringWord(localBranch, CL_GREEN))
.append("...")
.append(coloringWord(trackingBranch, CL_RED))
.append(" ")
.append(aheadBehind(repo));

var statusByFiles = new HashMap<String, List<FileStatus>>();

Function<FileStatus, Consumer<String>> computeStatus = s -> f -> {
var value = statusByFiles.computeIfAbsent(f, v -> new ArrayList<FileStatus>());
value.add(s);
statusByFiles.put(f, value);
};

// TODO how do we know of file rename status
status.getModified().stream().forEach(computeStatus.apply(MODIFIED));
status.getChanged().stream().forEach(computeStatus.apply(CHANGED));
status.getAdded().stream().forEach(computeStatus.apply(ADDED));
status.getMissing().stream().forEach(computeStatus.apply(MISSING));
status.getRemoved().stream().forEach(computeStatus.apply(REMOVED));
status.getUntracked().stream().forEach(computeStatus.apply(UNTRACKED));
status.getConflicting().stream().forEach(computeStatus.apply(CONFLICT));
status.getUntrackedFolders().stream().forEach(computeStatus.apply(UNTRACKED_DIRS));

statusByFiles.forEach((k, v) -> result.append(GisStringUtils.NEWLINE)
.append(" ")
.append(FileStatus.toPrint(k, v)));
return result.toString();
}

public static String status(Path f, boolean oneLiner) throws IOException, GitAPIException {
var repo = openRepo(f.resolve(".git"));
Status status;
try (var git = new Git(repo)) {
status = git.status()
.setIgnoreSubmodules(IgnoreSubmoduleMode.ALL)
.call();
}
if (status == null) {
return "";
}
var result = new StringBuilder(infof(getDirectoryName(repo.getWorkTree().toPath())));
var localBranch = repo.getBranch();
result.append(oneLiner
? statusOneLiner(repo, status, localBranch)
: statusFull(repo, status, localBranch));
return result.toString();
}
}
25 changes: 24 additions & 1 deletion src/main/java/org/nqm/command/Wrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.nqm.config.GisLog;
import org.nqm.GisException;
import org.nqm.config.GisLog;
import org.nqm.utils.StdOutUtils;

public final class Wrapper {
Expand Down Expand Up @@ -69,4 +70,26 @@ public static void forEachModuleWith(Predicate<Path> pred, String... args) throw
public static void forEachModuleDo(String... args) throws IOException {
forEachModuleWith(p -> true, args);
}

public static void forEachModuleStatus(Consumer<Path> gitStatus) throws IOException {
var gitModulesFilePath = getFileMarker();
var currentDir = currentDir();
try (var exe = Executors.newVirtualThreadPerTaskExecutor()) {
Optional.of(Path.of(currentDir))
.ifPresent(root -> exe.submit(() -> gitStatus.accept(root)));
Files.readAllLines(gitModulesFilePath.toPath()).stream()
.map(String::trim)
.filter(s -> s.startsWith("path"))
.map(s -> s.replace("path = ", ""))
.map(dir -> Path.of(currentDir, dir))
.filter(dir -> {
if (dir.toFile().exists()) {
return true;
}
StdOutUtils.errln("directory '%s' does not exist, will be ignored!".formatted("" + dir));
return false;
})
.forEach(dir -> exe.submit(() -> gitStatus.accept(dir)));
}
}
}
Loading
Loading