001/*
002 * JDrupes GitVersioning
003 * Copyright (C) 2025 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.gitversioning.api;
020
021import java.nio.file.Path;
022import java.util.List;
023import java.util.Objects;
024import java.util.ServiceLoader;
025import java.util.ServiceLoader.Provider;
026import java.util.stream.Stream;
027import org.eclipse.jgit.api.Git;
028import org.eclipse.jgit.api.Status;
029import org.eclipse.jgit.api.errors.GitAPIException;
030import org.eclipse.jgit.lib.Repository;
031
032/**
033 * Defines a configurable version evaluator.
034 */
035public interface VersionEvaluator {
036
037    /**
038     * Creates a version evaluator for the given repository. The
039     * implementation is looked up using the [ServiceLoader] mechanism
040     * with the given class loader.
041     *
042     * @param repository the repository
043     * @param classLoader the class loader
044     * @return the version evaluator
045     */
046    static VersionEvaluator forRepository(Repository repository,
047            ClassLoader classLoader) {
048        ServiceLoader<VersionEvaluatorProvider> loader
049            = ServiceLoader.load(VersionEvaluatorProvider.class, classLoader);
050        return loader.findFirst().orElseThrow().repository(repository);
051    }
052
053    /**
054     * Creates a version evaluator for the given repository. The
055     * implementation is looked up using the [ServiceLoader] mechanism,
056     * using the class loaders of the current thread and the
057     * [VersionEvaluator] class.
058     *
059     * @param repository the repository
060     * @return the version evaluator
061     */
062    static VersionEvaluator forRepository(Repository repository) {
063        return Stream.of(Thread.currentThread().getContextClassLoader(),
064            VersionEvaluator.class.getClassLoader()).filter(Objects::nonNull)
065            .map(cl -> ServiceLoader.load(VersionEvaluatorProvider.class, cl)
066                .stream())
067            .flatMap(s -> s).findFirst().map(Provider::get).orElseThrow()
068            .repository(repository);
069    }
070
071    /**
072     * Searches the sub directory for changes (any of combines added,
073     * changed, removed, missing, modified, conflicting or untracked). 
074     *
075     * @param repository the repository
076     * @param subDir the sub dir
077     * @return true, if is dirty
078     * @throws GitAPIException the git API exception
079     */
080    static List<Path> dirtyFiles(Repository repository, Path subDir)
081            throws GitAPIException {
082        try (Git git = Git.wrap(repository)) {
083            Status status = git.status().call();
084            var useAll = subDir == null || Path.of("").equals(subDir);
085
086            // Uncommitted combines added, changed, removed, missing,
087            // modified and conflicting
088            return Stream.concat(status.getUncommittedChanges().stream(),
089                status.getUntracked().stream()).map(Path::of)
090                .filter(path -> useAll || path.startsWith(subDir))
091                .toList();
092        }
093    }
094
095    /**
096     * Sets the sub directory of the work tree that is relevant for
097     * evaluating the version. Usually, only files in this directory are
098     * checked for changes, resulting in a "dirty" in the version.
099     *
100     * @param subDirectory the sub directory
101     * @return the version evaluator
102     */
103    VersionEvaluator subDirectory(Path subDirectory);
104
105    /**
106     * Sets the tag filter to use.
107     *
108     * @param tagFilter the tag filter
109     * @return the version evaluator
110     */
111    VersionEvaluator tagFilter(TagFilter tagFilter);
112
113    /**
114     * Sets the tag processor to use.
115     *
116     * @param tagProcessor the tag processor
117     * @return the version evaluator
118     */
119    VersionEvaluator tagProcessor(TagProcessor tagProcessor);
120
121    /**
122     * Returns the evaluated version.
123     *
124     * @return the string
125     */
126    String version();
127}