org.craftercms.studio.impl.v1.repository.git.GitContentRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.craftercms.studio.impl.v1.repository.git.GitContentRepository.java

Source

/*
 * Crafter Studio Web-content authoring solution
 * Copyright (C) 2007-2015 Crafter Software Corporation.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.craftercms.studio.impl.v1.repository.git;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.craftercms.studio.api.v1.exception.ContentNotFoundException;
import org.craftercms.studio.api.v1.log.Logger;
import org.craftercms.studio.api.v1.log.LoggerFactory;
import org.craftercms.studio.api.v1.repository.ContentRepository;
import org.craftercms.studio.api.v1.repository.RepositoryItem;
import org.craftercms.studio.api.v1.to.VersionTO;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.*;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.storage.file.LockFile;
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.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class GitContentRepository implements ContentRepository, ServletContextAware {

    private static final Logger logger = LoggerFactory.getLogger(GitContentRepository.class);

    private static String[] IGNORE_FILES = new String[] { ".keep", ".DS_Store" };

    /*
        private void addDebugStack() {
        Thread thread = Thread.currentThread();
        String threadName = thread.getName();
        logger.error("Thread: " + threadName);
        StackTraceElement[] stackTraceElements = thread.getStackTrace();
        StringBuilder sbStack = new StringBuilder();
        int stackSize = (10 < stackTraceElements.length-2) ? 10 : stackTraceElements.length;
        for (int i = 2; i < stackSize+2; i++){
            sbStack.append("\n\t").append(stackTraceElements[i].toString());
        }
        RequestContext context = RequestContext.getCurrent();
        CronJobContext cronJobContext = CronJobContext.getCurrent();
        if (context != null) {
            HttpServletRequest request = context.getRequest();
            String url = request.getRequestURI() + "?" + request.getQueryString();
            logger.error("Http request: " + url);
        } else if (cronJobContext != null) {
            logger.error("Cron Job");
        
        }
        logger.error("TRACE: Stack trace (depth 10): " + sbStack.toString());
        }
    */
    @Override
    public boolean contentExists(String site, String path) {
        Repository repo = null;
        try {
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            RevTree tree = getTree(repo);
            TreeWalk tw = TreeWalk.forPath(repo, getGitPath(path), tree);
            if (tw != null && tw.getObjectId(0) != null) {
                return true;
            }
        } catch (IOException e) {
            logger.info("Content not found for site: " + site + " path: " + path, e);
        }
        return false;
    }

    @Override
    public InputStream getContent(String site, String path) throws ContentNotFoundException {
        InputStream toRet = null;
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            RevTree tree = getTree(repo);
            TreeWalk tw = TreeWalk.forPath(repo, getGitPath(path), tree);

            ObjectId id = tw.getObjectId(0);
            ObjectLoader objectLoader = repo.open(id);
            toRet = objectLoader.openStream();

        } catch (IOException e) {
            logger.error("Error while getting content for file at site: " + site + " path: " + path, e);
        }
        return toRet;
    }

    @Override
    public boolean writeContent(String site, String path, InputStream content) {
        boolean success = true;

        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            String gitPath = getGitPath(path);
            RevTree tree = getTree(repo);
            TreeWalk tw = TreeWalk.forPath(repo, gitPath, tree);

            FS fs = FS.detect();
            File repoRoot = repo.getWorkTree();
            Path filePath = null;
            if (tw == null) {
                filePath = Paths.get(fs.normalize(repoRoot.getPath()), gitPath);
            } else {
                filePath = Paths.get(fs.normalize(repoRoot.getPath()), tw.getPathString());
            }

            if (!Files.exists(filePath)) {
                filePath = Files.createFile(filePath);
                Git git = new Git(repo);
                git.add().addFilepattern(gitPath).call();
            }

            File file = filePath.toFile();
            File folder = file.getParentFile();
            if (folder != null && !folder.exists()) {
                folder.mkdirs();
            }
            FileUtils.writeByteArrayToFile(file, IOUtils.toByteArray(content));
        } catch (IOException | GitAPIException err) {
            logger.error("error writing file: site: " + site + " path: " + path, err);
            success = false;
        }
        return success;
    }

    @Override
    public boolean createFolder(String site, String path, String name) {
        boolean success = true;

        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            RevTree tree = getTree(repo);
            TreeWalk tw = TreeWalk.forPath(repo, getGitPath(path), tree);

            FS fs = FS.detect();
            File repoRoot = repo.getWorkTree();
            Path folderPath = Paths.get(fs.normalize(repoRoot.getPath()), tw.getPathString(), name);
            Path keepPath = Paths.get(folderPath.toString(), ".keep");
            Files.createDirectories(folderPath);
            File keep = Files.createFile(keepPath).toFile();
            String gitPath = Paths.get(tw.getPathString(), name, ".keep").toString();
            Git git = new Git(repo);
            git.add().addFilepattern(gitPath).call();

            RevCommit commit = git.commit().setOnly(getGitPath(gitPath)).setMessage(StringUtils.EMPTY).call();
        } catch (IOException | GitAPIException e) {
            logger.error("Error creating folder " + name + " for site " + site + " at path " + path, e);
            success = false;
        }

        return success;
    }

    @Override
    public boolean deleteContent(String site, String path) {
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            Git git = new Git(repo);
            git.rm().addFilepattern(getGitPath(path)).setCached(false).call();

            RevCommit commit = git.commit().setOnly(getGitPath(path)).setMessage(StringUtils.EMPTY).call();
            return true;
        } catch (IOException | GitAPIException e) {
            logger.error("Error while deleting content for site: " + site + " path: " + path, e);
        }
        return false;
    }

    @Override
    public boolean moveContent(String site, String fromPath, String toPath) {
        return moveContent(site, fromPath, toPath, null);
    }

    @Override
    public boolean moveContent(String site, String fromPath, String toPath, String newName) {
        boolean success = true;

        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            String gitFromPath = getGitPath(fromPath);
            RevTree fromTree = getTree(repo);
            TreeWalk fromTw = TreeWalk.forPath(repo, gitFromPath, fromTree);

            String gitToPath = getGitPath(toPath);
            RevTree toTree = getTree(repo);
            TreeWalk toTw = TreeWalk.forPath(repo, gitToPath, toTree);

            FS fs = FS.detect();
            File repoRoot = repo.getWorkTree();
            Path sourcePath = null;
            if (fromTw == null) {
                sourcePath = Paths.get(fs.normalize(repoRoot.getPath()), gitFromPath);
            } else {
                sourcePath = Paths.get(fs.normalize(repoRoot.getPath()), fromTw.getPathString());
            }
            Path targetPath = null;
            if (toTw == null) {
                targetPath = Paths.get(fs.normalize(repoRoot.getPath()), gitToPath);
            } else {
                targetPath = Paths.get(fs.normalize(repoRoot.getPath()), toTw.getPathString());
            }

            File source = sourcePath.toFile();
            File destDir = targetPath.toFile();
            File dest = destDir;
            if (StringUtils.isNotEmpty(newName)) {
                dest = new File(destDir, newName);
            }
            if (source.isDirectory()) {
                File[] dirList = source.listFiles();
                for (File file : dirList) {
                    if (file.isDirectory()) {
                        FileUtils.moveDirectoryToDirectory(file, dest, true);
                    } else {
                        FileUtils.moveFileToDirectory(file, dest, true);
                    }
                }
                source.delete();
            } else {
                if (dest.isDirectory()) {
                    FileUtils.moveFileToDirectory(source, dest, true);
                } else {
                    source.renameTo(dest);
                }
            }
            Git git = new Git(repo);
            git.add().addFilepattern(gitToPath).call();
            git.rm().addFilepattern(gitFromPath).call();
            RevCommit commit = git.commit().setOnly(gitFromPath).setOnly(gitToPath).setMessage(StringUtils.EMPTY)
                    .call();
        } catch (IOException | GitAPIException err) {
            // log this error
            logger.error("Error while moving content from {0} to {1}", err, fromPath, toPath);
            success = false;
        }

        return success;
    }

    @Override
    public boolean copyContent(String site, String fromPath, String toPath) {
        boolean success = true;

        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            String gitFromPath = getGitPath(fromPath);
            RevTree fromTree = getTree(repo);
            TreeWalk fromTw = TreeWalk.forPath(repo, gitFromPath, fromTree);

            String gitToPath = getGitPath(toPath);
            RevTree toTree = getTree(repo);
            TreeWalk toTw = TreeWalk.forPath(repo, gitToPath, toTree);

            FS fs = FS.detect();
            File repoRoot = repo.getWorkTree();
            Path sourcePath = null;
            if (fromTw == null) {
                sourcePath = Paths.get(fs.normalize(repoRoot.getPath()), gitFromPath);
            } else {
                sourcePath = Paths.get(fs.normalize(repoRoot.getPath()), fromTw.getPathString());
            }
            Path targetPath = null;
            if (toTw == null) {
                targetPath = Paths.get(fs.normalize(repoRoot.getPath()), gitToPath);
            } else {
                targetPath = Paths.get(fs.normalize(repoRoot.getPath()), toTw.getPathString());
            }
            File sourceFile = sourcePath.toFile();
            if (sourceFile.isDirectory()) {
                FileUtils.copyDirectory(sourceFile, targetPath.toFile());
            } else {
                FileUtils.copyFileToDirectory(sourceFile, targetPath.toFile());
            }
            Git git = new Git(repo);
            git.add().addFilepattern(gitToPath).call();
        } catch (IOException | GitAPIException err) {
            // log this error
            logger.error("Error while copping content from {0} to {1}", err, fromPath, toPath);
            success = false;
        }

        return success;
    }

    @Override
    public RepositoryItem[] getContentChildren(String site, String path) {
        final List<RepositoryItem> retItems = new ArrayList<RepositoryItem>();
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            RevTree tree = getTree(repo);
            TreeWalk tw = TreeWalk.forPath(repo, getGitPath(path), tree);
            if (tw != null) {
                ObjectLoader loader = repo.open(tw.getObjectId(0));
                if (loader.getType() == Constants.OBJ_TREE) {
                    int depth = tw.getDepth();
                    tw.enterSubtree();
                    while (tw.next()) {
                        if (tw.getDepth() == depth + 1) {

                            RepositoryItem item = new RepositoryItem();
                            item.name = tw.getNameString();

                            String visitFolderPath = "/" + tw.getPathString();
                            loader = repo.open(tw.getObjectId(0));
                            item.isFolder = loader.getType() == Constants.OBJ_TREE;
                            int lastIdx = visitFolderPath.lastIndexOf(File.separator + item.name);
                            if (lastIdx > 0) {
                                item.path = visitFolderPath.substring(0, lastIdx);
                            }

                            if (!ArrayUtils.contains(IGNORE_FILES, item.name)) {
                                retItems.add(item);
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            logger.error("Error while getting children for site: " + site + " path: " + path, e);
        }

        RepositoryItem[] items = new RepositoryItem[retItems.size()];
        items = retItems.toArray(items);
        return items;
    }

    @Override
    public RepositoryItem[] getContentChildren(String site, String path, boolean ignoreCache) {
        return getContentChildren(site, path);
    }

    @Override
    public VersionTO[] getContentVersionHistory(String site, String path) {
        List<VersionTO> versionHistory = new ArrayList<VersionTO>();
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            ObjectId head = repo.resolve(Constants.HEAD);
            String gitPath = getGitPath(path);
            Git git = new Git(repo);
            Iterable<RevCommit> commits = git.log().add(head).addPath(gitPath).call();
            Iterator<RevCommit> iterator = commits.iterator();
            while (iterator.hasNext()) {
                RevCommit revCommit = iterator.next();
                VersionTO versionTO = new VersionTO();
                versionTO.setVersionNumber(revCommit.getId().toString());
                versionTO.setLastModifier(revCommit.getAuthorIdent().getName());
                versionTO.setLastModifiedDate(new Date(revCommit.getCommitTime() * 1000));
                versionTO.setComment(revCommit.getShortMessage());
                versionHistory.add(versionTO);
            }
        } catch (IOException | GitAPIException err) {
            logger.error("error while getting history for content item " + path);
        }
        VersionTO[] toRet = new VersionTO[versionHistory.size()];
        return versionHistory.toArray(toRet);
    }

    @Override
    public String createVersion(String site, String path, boolean majorVersion) {
        return createVersion(site, path, StringUtils.EMPTY, majorVersion);
    }

    @Override
    public String createVersion(String site, String path, String comment, boolean majorVersion) {
        String toRet = StringUtils.EMPTY;
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }

            String gitPath = getGitPath(path);
            Git git = new Git(repo);

            Status status = git.status().addPath(gitPath).call();

            if (status.hasUncommittedChanges() || !status.isClean()) {
                RevCommit commit = git.commit().setOnly(gitPath).setMessage(comment).call();
                toRet = commit.getId().toString();
            }
        } catch (IOException | GitAPIException err) {
            logger.error("error creating new version for site:  " + site + " path: " + path, err);
        }
        return toRet;
    }

    @Override
    public boolean revertContent(String site, String path, String version, boolean major, String comment) {
        boolean success = false;
        try {
            InputStream versionContent = getContentVersion(site, path, version);
            writeContent(site, path, versionContent);
            createVersion(site, path, major);
            success = true;
        } catch (ContentNotFoundException err) {
            logger.error("error reverting content for site:  " + site + " path: " + path, err);
        }
        return success;
    }

    @Override
    public InputStream getContentVersion(String site, String path, String version) throws ContentNotFoundException {
        InputStream toRet = null;
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            RevTree tree = getTreeForCommit(repo, version);
            TreeWalk tw = TreeWalk.forPath(repo, getGitPath(path), tree);

            ObjectId id = tw.getObjectId(0);
            ObjectLoader objectLoader = repo.open(id);
            toRet = objectLoader.openStream();

        } catch (IOException err) {
            logger.error("Error while getting content for file at site: " + site + " path: " + path, err);
        }
        return toRet;
    }

    @Override
    public Date getModifiedDate(String site, String path) {
        throw new RuntimeException("Not Implemented");
    }

    @Override
    public void lockItem(String site, String path) {
        try {
            Repository repo = getSiteRepositoryInstance(site);
            TreeWalk tw = new TreeWalk(repo);
            RevTree tree = getTree(repo);
            tw.addTree(tree); // tree 0
            tw.setRecursive(false);
            tw.setFilter(PathFilter.create(path));

            if (!tw.next()) {
                return;
            }

            FS fs = FS.detect();
            File repoRoot = repo.getWorkTree();
            Path path1 = Paths.get(fs.normalize(repoRoot.getPath()), tw.getPathString());
            File file = new File(tw.getPathString());
            LockFile lock = new LockFile(file, fs);
            lock.lock();

            tw.close();

        } catch (IOException e) {
            logger.error("Error while locking file for site: " + site + " path: " + path, e);
        }
    }

    @Override
    public void unLockItem(String site, String path) {
        try {
            Repository repo;
            if (StringUtils.isEmpty(site)) {
                repo = getGlobalConfigurationRepositoryInstance();
            } else {
                repo = getSiteRepositoryInstance(site);
            }
            RevTree tree = getTree(repo);
            TreeWalk tw = TreeWalk.forPath(repo, getGitPath(path), tree);
            if (tw != null) {
                FS fs = FS.detect();
                File file = new File(tw.getPathString());
                LockFile lock = new LockFile(file, fs);
                lock.unlock();

                tw.close();
            }
        } catch (IOException e) {
            logger.error("Error while locking file for site: " + site + " path: " + path, e);
        }
    }

    private Repository getGlobalConfigurationRepositoryInstance() throws IOException {

        Path siteRepoPath = Paths.get(rootPath, "global-configuration", ".git");
        if (Files.exists(siteRepoPath)) {
            return openGitRepository(siteRepoPath);
        } else {
            Files.deleteIfExists(siteRepoPath);
            //return cloneRemoteRepository(siteConfiguration.getGitRepositoryUrl(), siteConfiguration.getLocalRepositoryRoot());
        }
        return null;
    }

    private Repository getSiteRepositoryInstance(String site) throws IOException {

        Path siteRepoPath = Paths.get(rootPath, "sites", site, ".git");
        if (Files.exists(siteRepoPath)) {
            return openGitRepository(siteRepoPath);
        } else {
            Files.deleteIfExists(siteRepoPath);
            //return cloneRemoteRepository(siteConfiguration.getGitRepositoryUrl(), siteConfiguration.getLocalRepositoryRoot());
        }
        return null;
    }

    private Repository openGitRepository(Path repositoryPath) throws IOException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(repositoryPath.toFile()).readEnvironment().findGitDir().build();
        return repository;
    }

    private RevTree getTree(Repository repository)
            throws AmbiguousObjectException, IncorrectObjectTypeException, IOException, MissingObjectException {
        ObjectId lastCommitId = repository.resolve(Constants.HEAD);

        // a RevWalk allows to walk over commits based on some filtering
        try (RevWalk revWalk = new RevWalk(repository)) {
            RevCommit commit = revWalk.parseCommit(lastCommitId);

            // and using commit's tree find the path
            RevTree tree = commit.getTree();
            return tree;
        }
    }

    private RevTree getTreeForCommit(Repository repository, String commitId)
            throws AmbiguousObjectException, IncorrectObjectTypeException, IOException, MissingObjectException {
        ObjectId commitObjectId = repository.resolve(commitId);

        // a RevWalk allows to walk over commits based on some filtering
        try (RevWalk revWalk = new RevWalk(repository)) {
            RevCommit commit = revWalk.parseCommit(commitObjectId);

            // and using commit's tree find the path
            RevTree tree = commit.getTree();
            return tree;
        }
    }

    private String getGitPath(String path) {
        String gitPath = path.replaceAll("/+", "/");
        gitPath = gitPath.replaceAll("^/", "");
        return gitPath;
    }

    public void createPatch() {
        try (Repository repository = getSiteRepositoryInstance("gitreposite")) {
            try (Git git = new Git(repository)) {

                // the diff works on TreeIterators, we prepare two for the two branches
                AbstractTreeIterator oldTreeParser = prepareTreeParser(repository, "refs/remotes/origin/master");
                AbstractTreeIterator newTreeParser = prepareTreeParser(repository, "refs/heads/master");

                // then the procelain diff-command returns a list of diff entries
                List<DiffEntry> diff = git.diff().setOldTree(oldTreeParser).setNewTree(newTreeParser).call();
                OutputStream os = new FileOutputStream("/Users/dejanbrkic/gitpatchtest.diff");
                DiffFormatter df = new DiffFormatter(new SafeBufferedOutputStream(os));
                df.setRepository(repository);
                df.format(diff);
                df.flush();
                df.close();
                os.close();
            } catch (GitAPIException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private AbstractTreeIterator prepareTreeParser(Repository repository, String ref)
            throws IOException, MissingObjectException, IncorrectObjectTypeException {
        // from the commit we can build the tree which allows us to construct the TreeParser
        Ref head = repository.exactRef(ref);
        try (RevWalk walk = new RevWalk(repository)) {
            RevCommit commit = walk.parseCommit(head.getObjectId());
            RevTree tree = walk.parseTree(commit.getTree().getId());

            CanonicalTreeParser oldTreeParser = new CanonicalTreeParser();
            try (ObjectReader oldReader = repository.newObjectReader()) {
                oldTreeParser.reset(oldReader, tree.getId());
            }

            walk.dispose();

            return oldTreeParser;
        }
    }

    @Override
    public boolean createSiteFromBlueprint(String blueprintName, String siteId) {
        // create git repository for site content
        createSiteRepository(siteId);
        // copy files from blueprint
        copyContentFromBlueprint(blueprintName, siteId);
        // commit everything so it is visible
        createInitialCommit(siteId);

        return true;
    }

    private boolean createSiteRepository(String site) {
        boolean success = true;
        Path siteRepoPath = Paths.get(rootPath, "sites", site);
        try {
            Files.deleteIfExists(siteRepoPath);
            siteRepoPath = Paths.get(siteRepoPath.toAbsolutePath().toString(), ".git");
            Repository repository = FileRepositoryBuilder.create(siteRepoPath.toFile());
            repository.create();
        } catch (IOException e) {
            logger.error("Error while creating repository for site " + site, e);
            success = false;
        }
        return success;
    }

    private void copyContentFromBlueprint(String blueprint, String site) {
        Path siteRepoPath = Paths.get(rootPath, "sites", site);
        Path blueprintPath = Paths.get(rootPath, "global-configuration", "blueprints", blueprint);
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        TreeCopier tc = new TreeCopier(blueprintPath, siteRepoPath);
        try {
            Files.walkFileTree(blueprintPath, opts, Integer.MAX_VALUE, tc);
        } catch (IOException err) {
            logger.error("Error copping files from blueprint", err);
        }
    }

    class TreeCopier implements FileVisitor<Path> {
        private final Path source;
        private final Path target;

        TreeCopier(Path source, Path target) {
            this.source = source;
            this.target = target;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            CopyOption[] options = new CopyOption[0];

            Path newdir = target.resolve(source.relativize(dir));
            try {
                Files.copy(dir, newdir, options);
            } catch (FileAlreadyExistsException x) {
                // ignore
            } catch (IOException err) {
                logger.error("Unable to create: %s: %s%n", newdir, err);
                return SKIP_SUBTREE;
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            CopyOption[] options = new CopyOption[] { REPLACE_EXISTING };
            try {
                Files.copy(file, target.resolve(source.relativize(file)), options);
            } catch (IOException err) {
                logger.error("Unable to copy: " + source + " to " + target.resolve(source.relativize(file)), err);
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            if (exc instanceof FileSystemLoopException) {
                logger.error("cycle detected: " + file);
            } else {
                logger.error("Unable to copy: %s: %s%n", file, exc);
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            return CONTINUE;
        }
    }

    private void createInitialCommit(String site) {
        String toRet = StringUtils.EMPTY;
        try {
            Repository repo = getSiteRepositoryInstance(site);

            Git git = new Git(repo);

            Status status = git.status().call();

            if (status.hasUncommittedChanges() || !status.isClean()) {
                DirCache dirCache = git.add().addFilepattern(".").call();
                RevCommit commit = git.commit().setMessage("initial content").call();
                toRet = commit.getId().toString();
            }
        } catch (IOException | GitAPIException err) {
            logger.error("error creating initial commit for site:  " + site, err);
        }
    }

    /**
     * bootstrap the repository
     */
    public void bootstrap() throws Exception {
        Path globalConfigFolder = Paths.get(rootPath, "global-configuration");
        boolean bootstrapCheck = Files.exists(globalConfigFolder);

        if (bootstrapEnabled && !bootstrapCheck) {
            try {
                logger.error("Bootstrapping repository for Crafter CMS");
                Files.createDirectories(globalConfigFolder);
            } catch (Exception alreadyExistsErr) {
                // do nothing.
            }
            try {
                Path globalConfigRepoPath = Paths.get(globalConfigFolder.toAbsolutePath().toString(), ".git");
                Repository repository = FileRepositoryBuilder.create(globalConfigRepoPath.toFile());
                repository.create();
            } catch (IOException e) {
                logger.error("Error while creating global configuration repository", e);
            }

            String bootstrapFolderPath = this.ctx.getRealPath(File.separator + "gitrepo-bootstrap");
            Path source = java.nio.file.FileSystems.getDefault().getPath(bootstrapFolderPath);

            logger.info("Bootstrapping with baseline @ " + source.toFile().toString());

            Path target = Paths.get(rootPath);

            TreeCopier tc = new TreeCopier(source, target);
            EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
            Files.walkFileTree(source, opts, Integer.MAX_VALUE, tc);

            try {
                Repository globalConfigRepo = getGlobalConfigurationRepositoryInstance();

                Git git = new Git(globalConfigRepo);

                Status status = git.status().call();

                if (status.hasUncommittedChanges() || !status.isClean()) {
                    DirCache dirCache = git.add().addFilepattern(".").call();
                    RevCommit commit = git.commit().setMessage("initial content").call();
                    String tmp = commit.getId().toString();
                }
            } catch (IOException | GitAPIException err) {
                logger.error("error creating initial commit for global configuration", err);
            }
        }
    }

    public String getRootPath() {
        return rootPath;
    }

    public void setRootPath(String path) {
        rootPath = path;
    }

    public boolean isBootstrapEnabled() {
        return bootstrapEnabled;
    }

    public void setBootstrapEnabled(boolean bootstrapEnabled) {
        this.bootstrapEnabled = bootstrapEnabled;
    }

    public void setServletContext(ServletContext ctx) {
        this.ctx = ctx;
    }

    String rootPath;
    boolean bootstrapEnabled;
    ServletContext ctx;
}