All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.exactpro.th2.infrarepo.git.Gitter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020-2021 Exactpro (Exactpro Systems Limited)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.exactpro.th2.infrarepo.git;

import com.exactpro.th2.infrarepo.InconsistentRepositoryStateException;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.errors.EntryExistsException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.transport.*;
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
import org.eclipse.jgit.transport.sshd.SshdSessionFactoryBuilder;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Gitter {
    public static final String REFS_HEADS = "refs/heads/";

    public static final int TIME_OUT = 10;

    Logger logger = LoggerFactory.getLogger(Gitter.class);

    private GitterContext ctx;

    private String branch;

    private Lock lock;

    private TransportConfigCallback callback;

    private final String localCacheRoot;

    private final String repositoryDir;

    Gitter(GitterContext ctx, String branch) {
        this.ctx = ctx;
        this.branch = branch;
        this.localCacheRoot = ctx.getLocalRepositoryRoot() + "/" + branch;
        this.repositoryDir = localCacheRoot + "/.git";
        this.callback = Gitter.transportConfigCallback(ctx);
        this.lock = new ReentrantLock();
    }

    /**
     * Return configuration that was used to construct this instance
     *
     * @return Configuration that was used to construct instance
     */
    public GitConfig getConfig() {
        return ctx;
    }

    /**
     * Return branch name for which this instance was constructed
     *
     * @return Branch name
     */
    public String getBranch() {
        return branch;
    }

    /**
     * Acquires lock for this branch to prevent other threads to work on same the branch.
     * Operations should be enclosed with try/finally block and lock should be released
     * as soon as operations are done on this branch
     *
     * 
     * {@code
     * Gitter gitter;
     * // retrieve instance
     * gitter.lock()
     * try {
     *     // do operations on repository
     * } finally {
     *     gitter.unlock();
     * }
     * }
     * 
*/ public void lock() { lock.lock(); } /** * Releases lock on this branch */ public void unlock() { lock.unlock(); } private static TransportConfigCallback transportConfigCallback(GitConfig config) { return transport -> { if (transport instanceof HttpTransport) { HttpTransport httpTransport = (HttpTransport) transport; httpTransport.setCredentialsProvider(new UsernamePasswordCredentialsProvider( config.getHttpAuthUsername(), config.getHttpAuthPassword())); httpTransport.setTimeout(TIME_OUT); } else if (transport instanceof SshTransport) { File sshDir = new File(config.getSshDir()); SshdSessionFactory sshSessionFactory = new SshdSessionFactoryBuilder() .setPreferredAuthentications("publickey") .setHomeDirectory(FS.DETECTED.userHome()) .setSshDirectory(sshDir) .build(null); SshTransport sshTransport = (SshTransport) transport; sshTransport.setTimeout(TIME_OUT); sshTransport.setSshSessionFactory(sshSessionFactory); } else { throw new RuntimeException( String.format("Unknown transport type (%s)", transport.getClass().getName())); } }; } static Map getAllBranchesCommits(GitterContext ctx) throws Exception { // retrieve all remote branches Collection allBranches = Git.lsRemoteRepository() .setHeads(true) .setRemote(ctx.getRemoteRepository()) .setTransportConfigCallback(Gitter.transportConfigCallback(ctx)) .call(); // filter, convert and normalize branch names Map result = new HashMap<>(); allBranches.stream() .filter(r -> r.getName().startsWith(REFS_HEADS)) .forEach(r -> result.put(r.getName().substring(REFS_HEADS.length()), r.getObjectId().getName())); return result; } static Set getBranches(GitterContext ctx) throws Exception { Map commits = getAllBranchesCommits(ctx); return commits.keySet(); } private String checkout(String branch, String targetDir) throws IOException, GitAPIException { // create branch directory if it does not exist File dir = new File(targetDir); if (!dir.exists() && !dir.mkdirs()) { throw new IOException(String.format("Error creating repository directory %s", targetDir)); } Repository repo = new FileRepository(repositoryDir); Git git = new Git(repo); // try to pull branch try { git .pull() .setStrategy(MergeStrategy.THEIRS) .setTransportConfigCallback(callback) .call(); } catch (NoHeadException e) { // probably there is no git repository in the directory // try to clone try (Git call = Git.cloneRepository() .setBranch(branch) .setURI(ctx.getRemoteRepository()) .setDirectory(dir) .setTransportConfigCallback(callback) .call()) { logger.info("local repository was not present, proceeding to clone"); } } catch (WrongRepositoryStateException e) { // try to recreate local repository recreateCache(); } Ref ref = git.checkout() .setName(branch) .setForced(true) .call(); return ref.getObjectId().getName(); } /** * Downloads to local cache latest version of the branch from remote repository and returns commit ref for * the latest commit * * @return commit ref for latest commit * @throws IOException * @throws GitAPIException */ public String checkout() throws IOException, GitAPIException { return checkout(branch, localCacheRoot); } /** * Resets repository's local working tree using repository's local copy. * This operation is equivalent of the command {@code git reset --hard} * * @return commit ref of current repository node * @throws InconsistentRepositoryStateException if operation fails for any reason and * repository's local copy's consistency can not be warranted */ public String reset() throws InconsistentRepositoryStateException { checkAndGetLocalCacheRoot(); try { Repository repo = new FileRepository(repositoryDir); Git git = new Git(repo); Ref ref = git.reset().setMode(ResetCommand.ResetType.HARD).call(); return ref.getObjectId().getName(); } catch (Exception e) { throw new InconsistentRepositoryStateException( String.format("Exception resetting repository for branch \"%s\"", branch) , e); } } /** * Recreates repository's local cache by deleting and re-downloading it from remote repository. * Can be used to repair de-synchronized local and remote repositories due to push or merge conflicts. * * @return commit ref of latest commit in repository * @throws IOException * @throws GitAPIException */ public String recreateCache() throws IOException, GitAPIException { File rootDir = checkAndGetLocalCacheRoot(); try { FileUtils.delete(rootDir, FileUtils.RECURSIVE); return checkout(); } catch (IOException ioe) { throw new IOException( String.format("Error deleting local repository cache for branch \"%s\"", branch), ioe); } } /** * Commits repository working tree and pushes changes to remote repository * * @param message commit message string * @return null if working tree was clean and no commit happened * or commit ref of latest commit in the remote repository * @throws InconsistentRepositoryStateException If commit or push failed and repository's local cache's * consistency can not be warranted * @throws IOException * @throws GitAPIException */ public String commitAndPush(String message) throws IOException, GitAPIException, InconsistentRepositoryStateException { checkAndGetLocalCacheRoot(); Repository repo = new FileRepository(repositoryDir); Git git = new Git(repo); if (git.status().call().isClean()) { return null; } git.add() .setUpdate(true) .addFilepattern(".") .call(); git.add() .addFilepattern(".") .call(); try { String commitRef = git.commit() .setMessage(message) .call() .getId() .getName(); String ref = Gitter.REFS_HEADS + branch; for (PushResult pushResult : git .push() .add(ref) .setForce(false) .setTransportConfigCallback(transportConfigCallback(ctx)) .call()) { RemoteRefUpdate update = pushResult.getRemoteUpdate(ref); if (update != null) { if (update.getStatus() == RemoteRefUpdate.Status.OK) { return commitRef; } else { throw new InconsistentRepositoryStateException( String.format("Exception pushing branch \"%s\" to remote: %s" , branch, update.getStatus().name())); } } } throw new InconsistentRepositoryStateException( String.format("Cannot determine result of push command for branch \"%s\"", branch)); } catch (InconsistentRepositoryStateException irse) { throw irse; } catch (Exception e) { throw new InconsistentRepositoryStateException( String.format("Exception with commit and push for branch \"%s\"", branch), e); } } /** * Creates new branch in local and remote repositories. If local cache contained leftovers of some branch which * does not exist anymore on remote repository then local cache will be deleted and new branch will overwrite it * * @param sourceBranch branch from which new branch will be created * @return commit ref of the new branch * @throws Exception */ public String createBranch(String sourceBranch) throws Exception { Set branches = getBranches(ctx); if (!branches.contains(sourceBranch)) { throw new IllegalArgumentException("Source branch does not exists"); } if (branches.contains(branch)) { throw new EntryExistsException("Branch with such name already exists"); } try { checkout(sourceBranch, localCacheRoot); Repository repo = new FileRepository(repositoryDir); Git git = new Git(repo); git.branchCreate() .setName(branch) .call(); Ref ref = git.checkout() .setName(branch) .call(); git.push() .add(ref) .setTransportConfigCallback(callback) .call(); return ref.getObjectId().getName(); } catch (Exception e) { try { FileUtils.delete(new File(localCacheRoot), FileUtils.RECURSIVE); } catch (IOException ioe) { throw new InconsistentRepositoryStateException( String.format("Could not delete local repository cache %s", localCacheRoot), e); } throw e; } } private File checkAndGetLocalCacheRoot() { File dir = new File(localCacheRoot); if (!dir.exists()) { throw new IllegalArgumentException("branch does not exist locally"); } return dir; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy