de.fkoeberle.autocommit.git.GitRepositoryAdapter.java Source code

Java tutorial

Introduction

Here is the source code for de.fkoeberle.autocommit.git.GitRepositoryAdapter.java

Source

/*
 * Copyright (C) 2012, Florian Kberle <florian@fkoeberle.de>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package de.fkoeberle.autocommit.git;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.egit.core.IteratorService;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;

import de.fkoeberle.autocommit.IRepository;
import de.fkoeberle.autocommit.message.MessagePluginActivator;
import de.fkoeberle.autocommit.message.Profile;
import de.fkoeberle.autocommit.message.ProfileManager;
import de.fkoeberle.autocommit.message.ProfileReferenceXml;
import de.fkoeberle.autocommit.message.ProfileXml;
import de.fkoeberle.autocommit.message.Session;

/**
 * Enhances an existing Git repository to match the interface
 * {@link IRepository}. It keeps only a weak reference to the repository, which
 * makes it possible to manage instances of this class in a WeakHashMap from
 * {@link Repository} to {@link GitRepositoryAdapter}.
 */
public class GitRepositoryAdapter implements IRepository {
    private final Repository repository;
    private final List<Object> sessionData;
    private byte[] sessionDataDeltaHash;

    public GitRepositoryAdapter(Repository repository) {
        this.repository = repository;
        this.sessionData = new ArrayList<Object>();
    }

    @Override
    public void commit() throws IOException {
        Git git = new Git(repository);
        stageForCommit(git, FilesToAdd.ADDED_OR_MODIFIED);
        stageForCommit(git, FilesToAdd.REMOVED_OR_MODIFIED);

        String message = buildCommitMessage();
        if (message != null) {
            commitStagedFiles(git, message);
        }
    }

    private void commitStagedFiles(Git git, String message) throws UnmergedPathException, IOException {
        CommitCommand commitCommand = git.commit();
        commitCommand.setMessage(message);

        try {
            commitCommand.call();
        } catch (GitAPIException e) {
            throw new IOException(e);
        }
    }

    private enum FilesToAdd {
        ADDED_OR_MODIFIED, REMOVED_OR_MODIFIED;
    }

    private void stageForCommit(Git git, FilesToAdd filesToAdd) {
        WorkingTreeIterator workingTreeIterator = IteratorService.createInitialIterator(repository);
        try {
            AddCommand addCommand = git.add();
            addCommand.addFilepattern(".");
            addCommand.setWorkingTreeIterator(workingTreeIterator);
            addCommand.setUpdate(filesToAdd == FilesToAdd.REMOVED_OR_MODIFIED);
            addCommand.call();
        } catch (NoFilepatternException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean noUncommittedChangesExist() throws IOException {
        AnyChangeDetectingDeltaVisitor changeDetector = new AnyChangeDetectingDeltaVisitor();
        visitHeadFileSystemDelta(repository, changeDetector);
        return !changeDetector.hasDetectedChange();
    }

    private String buildCommitMessage() throws IOException {
        final File commitMessagesFile = getProfileFile();
        final Profile profile = MessagePluginActivator.getProfile(commitMessagesFile);

        final ObjectReader reader = repository.newObjectReader();
        FileSetDeltaBuilder fileSetBuilder = new FileSetDeltaBuilder(reader);
        AnyChangeDetectingDeltaVisitor anyChangeDetectingDeltaVisitor = new AnyChangeDetectingDeltaVisitor();
        Session session = new Session();
        if (sessionDataDeltaHash != null) {
            HashCacluatingDeltaVisitor hashCalculator = new HashCacluatingDeltaVisitor();
            visitHeadIndexDelta(fileSetBuilder, anyChangeDetectingDeltaVisitor, hashCalculator);
            byte[] currentHash = hashCalculator.buildHash();
            if (Arrays.equals(sessionDataDeltaHash, currentHash)) {
                for (Object data : sessionData) {
                    session.add(data);
                }
            }
            sessionDataDeltaHash = null;
        } else {
            visitHeadIndexDelta(fileSetBuilder, anyChangeDetectingDeltaVisitor);
        }
        session.add(fileSetBuilder.build());
        if (anyChangeDetectingDeltaVisitor.hasDetectedChange()) {
            return profile.generateMessage(session);
        } else {
            return null;
        }
    }

    private File getProfileFile() {
        File repositoryDirectory = repository.getWorkTree();
        final File commitMessagesFile = new File(repositoryDirectory, ".commitmessages");
        return commitMessagesFile;
    }

    private ObjectId objectIdOrNullOfMatch(AbstractTreeIterator match) {
        ObjectId objectId = null;
        if (match != null) {
            objectId = match.getEntryObjectId();
        }
        return objectId;
    }

    void visitDeltaWithAll(String path, AbstractTreeIterator oldTreeMatch, AbstractTreeIterator newTreeMatch,
            GitFileSetDeltaVisitor... visitors) throws IOException {
        ObjectId oldObjectId = objectIdOrNullOfMatch(oldTreeMatch);
        ObjectId newObjectId = objectIdOrNullOfMatch(newTreeMatch);
        for (GitFileSetDeltaVisitor visitor : visitors) {
            if (newObjectId == null) {
                visitor.visitRemovedFile(path, oldObjectId);
            } else if (oldObjectId == null) {
                visitor.visitAddedFile(path, newObjectId);
            } else {
                visitor.visitChangedFile(path, oldObjectId, newObjectId);
            }
        }
    }

    private void visitHeadIndexDelta(GitFileSetDeltaVisitor... visitors) throws IOException {
        TreeWalk treeWalk = new TreeWalk(repository);
        treeWalk.setRecursive(true);
        ObjectId headTreeId = repository.resolve(Constants.HEAD);
        DirCache dirCache = repository.readDirCache();
        DirCacheIterator dirCacheTree = new DirCacheIterator(dirCache);
        int dirCacheTreeIndex = treeWalk.addTree(dirCacheTree);

        if (headTreeId == null) {
            while (treeWalk.next()) {
                DirCacheIterator dirCacheMatch = treeWalk.getTree(dirCacheTreeIndex, DirCacheIterator.class);
                final String path = treeWalk.getPathString();
                ObjectId newObjectId = dirCacheMatch.getEntryObjectId();
                for (GitFileSetDeltaVisitor visitor : visitors) {
                    visitor.visitAddedFile(path, newObjectId);
                }
            }
        } else {
            RevWalk revWalk = new RevWalk(repository);
            RevTree revTree = revWalk.parseTree(headTreeId);
            int revTreeIndex = treeWalk.addTree(revTree);
            TreeFilter filter = AndTreeFilter.create(TreeFilter.ANY_DIFF,
                    new SkipWorkTreeFilter(dirCacheTreeIndex));
            treeWalk.setFilter(filter);

            while (treeWalk.next()) {
                AbstractTreeIterator headMatch = treeWalk.getTree(revTreeIndex, AbstractTreeIterator.class);
                DirCacheIterator dirCacheMatch = treeWalk.getTree(dirCacheTreeIndex, DirCacheIterator.class);
                final String path = treeWalk.getPathString();
                visitDeltaWithAll(path, headMatch, dirCacheMatch, visitors);
            }
        }
    }

    private void visitHeadFileSystemDelta(Repository repository, GitFileSetDeltaVisitor... visitors)
            throws IOException {
        TreeWalk treeWalk = new TreeWalk(repository);
        treeWalk.setRecursive(true);
        // Using the IteratorService is important
        // to for example automatically ignore class files in bin/
        WorkingTreeIterator fileTreeIterator = IteratorService.createInitialIterator(repository);
        int workTreeIndex = treeWalk.addTree(fileTreeIterator);

        ObjectId headTreeId = repository.resolve(Constants.HEAD);
        if (headTreeId == null) {
            while (treeWalk.next()) {
                WorkingTreeIterator fileTreeMatch = treeWalk.getTree(workTreeIndex, WorkingTreeIterator.class);
                final String path = treeWalk.getPathString();
                ObjectId newObjectId = fileTreeMatch.getEntryObjectId();
                for (GitFileSetDeltaVisitor visitor : visitors) {
                    visitor.visitAddedFile(path, newObjectId);
                }
            }
        } else {
            RevWalk revWalk = new RevWalk(repository);
            RevTree revTree = revWalk.parseTree(headTreeId);
            int revTreeIndex = treeWalk.addTree(revTree);
            treeWalk.setFilter(TreeFilter.ANY_DIFF);

            while (treeWalk.next()) {
                AbstractTreeIterator headMatch = treeWalk.getTree(revTreeIndex, AbstractTreeIterator.class);
                WorkingTreeIterator fileTreeMatch = treeWalk.getTree(workTreeIndex, WorkingTreeIterator.class);
                if (fileTreeMatch != null) {
                    if (fileTreeMatch.isEntryIgnored()) {
                        continue;
                    }
                }
                final String path = treeWalk.getPathString();
                visitDeltaWithAll(path, headMatch, fileTreeMatch, visitors);
            }
        }
    }

    @Override
    public void addSessionDataForUncommittedChanges(Object data) throws IOException {
        HashCacluatingDeltaVisitor hashCalculator = new HashCacluatingDeltaVisitor();

        visitHeadFileSystemDelta(repository, hashCalculator);

        byte[] currentHash = hashCalculator.buildHash();
        if (!Arrays.equals(currentHash, sessionDataDeltaHash)) {
            sessionData.clear();
        }
        sessionDataDeltaHash = currentHash;
        sessionData.add(data);
    }

    private void openEditorForProfileFile(File profileFile) throws IOException {
        URI profileFileURI = profileFile.toURI();
        IFileStore fileStore = EFS.getLocalFileSystem().getStore(profileFileURI);
        IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
        try {
            IDE.openEditorOnFileStore(page, fileStore);
        } catch (PartInitException e) {
            throw new IOException(e);
        }
    }

    public void editCommitMessagesFor(IProject project) throws IOException {
        File profileFile = getProfileFile();
        if (!profileFile.exists()) {
            createInitialProfileFile(profileFile);
        }
        openEditorForProfileFile(profileFile);
    }

    @Override
    public void prepareProjectForAutomaticCommits(IProject project) throws IOException {
        File profileFile = getProfileFile();
        if (!profileFile.exists()) {
            createInitialProfileFile(profileFile);
            openEditorForProfileFile(profileFile);
        }
    }

    private void createInitialProfileFile(File profileFile) throws IOException {
        ProfileReferenceXml profileReferenceXml = determineInitialProfileReferenceXml();
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(ProfileXml.class, ProfileReferenceXml.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.marshal(profileReferenceXml, profileFile);
        } catch (JAXBException e) {
            throw new IOException(e);
        }
    }

    private ProfileReferenceXml determineInitialProfileReferenceXml() throws IOException {
        ProfileReferenceXml profileReferenceXml = new ProfileReferenceXml();
        profileReferenceXml.setId(ProfileManager.DEFAULT_DEFAULT_PROFILE_ID);
        return profileReferenceXml;
    }

}