com.nlbhub.nlb.vcs.GitAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.nlbhub.nlb.vcs.GitAdapter.java

Source

/**
 * @(#)GitAdapter.java
 *
 * This file is part of the Non-Linear Book project.
 * Copyright (c) 2012-2014 Anton P. Kolosov
 * Authors: Anton P. Kolosov, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ANTON P. KOLOSOV. ANTON P. KOLOSOV DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the Non-Linear Book software without
 * disclosing the source code of your own applications.
 *
 * For more information, please contact Anton P. Kolosov at this
 * address: antokolos@gmail.com
 *
 * Copyright (c) 2014 Anton P. Kolosov All rights reserved.
 */
package com.nlbhub.nlb.vcs;

import com.nlbhub.nlb.api.ProgressData;
import com.nlbhub.nlb.exception.NLBVCSException;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.TreeWalk;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * The GitAdapter class
 * Uses sample code from https://github.com/centic9/jgit-cookbook
 *
 * @author Anton P. Kolosov
 * @version 1.0 1/9/14
 */
public class GitAdapter implements VCSAdapter {
    private Repository m_localRepo = null;
    private Git m_git = null;
    private Map<String, Status> m_statuses = new HashMap<>();
    private Author m_author;

    public GitAdapter(final Author author) {
        m_author = author;
    }

    @Override
    public void initRepo(String path) throws NLBVCSException {
        try {
            m_localRepo = FileRepositoryBuilder.create(new File(path, ".git"));
            enableLongPaths(m_localRepo, false);
            m_localRepo.create();
            m_git = new Git(m_localRepo);
            initStatuses(false);
            // This commit solves the problem with the incorrect HEAD revision, when files
            // were added but no commits has been done yet
            commit("Initial commit");
        } catch (IOException e) {
            throw new NLBVCSException("Error while Git repository initialization", e);
        }
    }

    private static void enableLongPaths(final Repository repository, final boolean save) throws IOException {
        StoredConfig config = repository.getConfig();
        config.setString("core", null, "longpaths", "true");
        if (save) {
            config.save();
        }
    }

    @Override
    public void openRepo(String path) throws NLBVCSException {
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            m_localRepo = (builder.setWorkTree(new File(path)).readEnvironment() // scan environment GIT_* variables
                    .setGitDir(new File(path, ".git")) // in fact, this can be omitted
                    .build());
            enableLongPaths(m_localRepo, true);
            m_git = new Git(m_localRepo);
            initStatuses(true);
        } catch (IOException e) {
            throw new NLBVCSException("Error while Git repository opening", e);
        }
    }

    @Override
    public void closeAdapter() throws NLBVCSException {
        if (m_git != null) {
            m_git.close();
            m_git = null;
        }
        if (m_localRepo != null) {
            m_localRepo.close();
            m_localRepo = null;
        }
        m_statuses.clear();
    }

    @Override
    public boolean getDirAddFlag() {
        return false;
    }

    @Override
    public boolean getAddModifiedFilesFlag() {
        return true;
    }

    private void initStatuses(boolean processExistentFiles) throws NLBVCSException {
        m_statuses.clear();
        try {
            if (processExistentFiles) {
                List<String> filePaths = listRepositoryContents();
                for (final String filePath : filePaths) {
                    // Initially mark all repository files as clean
                    // (i.e. under version control & without changes)
                    m_statuses.put(filePath, Status.Clean);
                }
            }
            org.eclipse.jgit.api.Status status = m_git.status().call();
            putItemsStatus(status.getAdded(), Status.Added);
            putItemsStatus(status.getChanged(), Status.Modified); // ???
            putItemsStatus(status.getModified(), Status.Modified);
            putItemsStatus(status.getConflicting(), Status.Conflict);
            //System.out.println("ConflictingStageState: " + status.getConflictingStageState());
            putItemsStatus(status.getIgnoredNotInIndex(), Status.Ignored);
            putItemsStatus(status.getMissing(), Status.Missing);
            putItemsStatus(status.getRemoved(), Status.Removed);
            putItemsStatus(status.getUntracked(), Status.Unknown);
            putItemsStatus(status.getUntrackedFolders(), Status.Unknown);
        } catch (IOException | GitAPIException e) {
            throw new NLBVCSException("Error while obtaining Git repository status", e);
        }
    }

    private List<String> listRepositoryContents() throws IOException, NLBVCSException {
        List<String> result = new ArrayList<>();
        Ref head = m_localRepo.getRef(Constants.HEAD);
        // head can be null if repo is broken (for example, .git directory was deleted)
        if (head == null) {
            throw new NLBVCSException(
                    "Your Git repository is broken.\nPlease either properly clone it (using 'git clone' command) or use 'runlight' command to run the program.");
        }

        // head.getObjectId() can be null, for example, if repository was never committed.
        final ObjectId objectId = head.getObjectId();
        if (objectId != null) {
            // a RevWalk allows to walk over commits based on some filtering that is defined
            RevWalk walk = new RevWalk(m_localRepo);

            RevCommit commit = walk.parseCommit(objectId);
            RevTree tree = commit.getTree();

            // now use a TreeWalk to iterate over all files in the Tree recursively
            // you can set Filters to narrow down the results if needed
            TreeWalk treeWalk = new TreeWalk(m_localRepo);
            treeWalk.addTree(tree);
            treeWalk.setRecursive(false);
            while (treeWalk.next()) {
                if (treeWalk.isSubtree()) {
                    //System.out.println("dir: " + treeWalk.getPathString());
                    treeWalk.enterSubtree();
                } else {
                    //System.out.println("file: " + treeWalk.getPathString());
                    result.add(treeWalk.getPathString());
                }
            }
        }

        return result;
    }

    private void putItemsStatus(final Set<String> paths, final Status status) {
        for (final String path : paths) {
            m_statuses.put(path, status);
        }
    }

    @Override
    public Status getStatus(String path) throws NLBVCSException {
        Status status = m_statuses.get(path);
        if (status == null) {
            return Status.VCS_Undefined;
        }
        return status;
    }

    @Override
    public void add(String path) throws NLBVCSException {
        try {
            Status prevStatus = getStatus(path);
            m_git.add().addFilepattern(path).call();
            if (prevStatus == Status.Unknown || prevStatus == Status.VCS_Undefined) {
                // If file previously was unknown to the Git, we should mark it as added
                m_statuses.put(path, Status.Added);
            }
        } catch (GitAPIException e) {
            throw new NLBVCSException("Error while adding to the Git repository", e);
        }
    }

    @Override
    public boolean remove(String path) throws NLBVCSException {
        try {
            // cached - true if files should only be removed from index, false if files should also
            // be deleted from the working directory
            m_git.rm().addFilepattern(path).setCached(true).call();
            m_statuses.put(path, Status.Removed);
            return false;
        } catch (GitAPIException e) {
            throw new NLBVCSException("Error while removing from the Git repository", e);
        }
    }

    @Override
    public void reset(String path) throws NLBVCSException {
        try {
            Status status = getStatus(path);
            m_git.reset().addPath(path).setRef(Constants.HEAD).call();
            switch (status) {
            case Added:
                m_statuses.put(path, Status.Unknown);
                break;
            case Removed:
            case Modified:
            case Missing:
            case Clean:
                m_statuses.put(path, Status.Clean);
                break;
            case Unknown:
            case Ignored:
                throw new NLBVCSException("Cannot issue reset command for file with path = " + path
                        + ", because its status was " + status);
            }
        } catch (GitAPIException e) {
            throw new NLBVCSException("Error while adding to the Git repository", e);
        }
    }

    @Override
    public void commit(final String message) throws NLBVCSException {
        try {
            m_git.commit().setAll(false).setAmend(false).setAuthor(m_author.getName(), m_author.getEmail())
                    .setMessage(message).call();
        } catch (GitAPIException e) {
            throw new NLBVCSException("Error while committing to the Git repository", e);
        }
    }

    @Override
    public void pull(String userName, String password, ProgressData progressData) throws NLBVCSException {
        CredentialsProvider provider = new UsernamePasswordCredentialsProvider(userName, password);
        progressData.setNoteText("Pulling...");
        ProgressMonitor monitor = new VCSProgressMonitor(progressData);
        try {
            m_git.pull().setRemote("origin").setCredentialsProvider(provider).setProgressMonitor(monitor).call();
            // Analogous to `git clean -fd` command. It is needed to remove empty directories that can remain after
            // `git pull` command.
            m_git.clean().setCleanDirectories(true).setDryRun(false).setIgnore(true).call();
        } catch (GitAPIException e) {
            throw new NLBVCSException("Error while pulling from the Git repository: " + e.getMessage(), e);
        }
    }

    @Override
    public void push(String userName, String password, final ProgressData progressData) throws NLBVCSException {
        CredentialsProvider provider = new UsernamePasswordCredentialsProvider(userName, password);
        progressData.setNoteText("Pushing...");
        ProgressMonitor monitor = new VCSProgressMonitor(progressData);
        try {
            m_git.push().setRemote("origin").setCredentialsProvider(provider).setProgressMonitor(monitor).call();
        } catch (GitAPIException e) {
            throw new NLBVCSException("Error while pushing to the Git repository: " + e.getMessage(), e);
        }
    }
}