com.tasktop.c2c.server.scm.service.GitServiceBean.java Source code

Java tutorial

Introduction

Here is the source code for com.tasktop.c2c.server.scm.service.GitServiceBean.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2012 Tasktop Technologies
 * Copyright (c) 2010, 2011 SpringSource, a division of VMware
 * 
 * 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
 * 
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 ******************************************************************************/
package com.tasktop.c2c.server.scm.service;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;

import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.DeleteBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepository;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Component;

import com.tasktop.c2c.server.common.service.EntityNotFoundException;
import com.tasktop.c2c.server.common.service.domain.Region;
import com.tasktop.c2c.server.common.service.domain.Role;
import com.tasktop.c2c.server.common.service.query.QueryUtil;
import com.tasktop.c2c.server.common.service.web.TenancyUtil;
import com.tasktop.c2c.server.scm.domain.Blame;
import com.tasktop.c2c.server.scm.domain.Blob;
import com.tasktop.c2c.server.scm.domain.Commit;
import com.tasktop.c2c.server.scm.domain.Item;
import com.tasktop.c2c.server.scm.domain.Profile;
import com.tasktop.c2c.server.scm.domain.ScmLocation;
import com.tasktop.c2c.server.scm.domain.ScmRepository;
import com.tasktop.c2c.server.scm.domain.ScmSummary;
import com.tasktop.c2c.server.scm.domain.ScmType;
import com.tasktop.c2c.server.scm.domain.Trees;

@Component
public class GitServiceBean implements GitService, InitializingBean {

    @Autowired
    private ScmServiceConfiguration profileServiceConfiguration;

    @Autowired
    protected JgitRepositoryProvider repositoryProvider;

    @Value("${git.startThreads}")
    private boolean startThreads = true;

    private List<Commit> getLog(Repository repository, Region region, Set<ObjectId> visited) {
        List<Commit> result = new ArrayList<Commit>();

        for (RevCommit revCommit : getAllCommits(repository, region, visited)) {
            Commit commit = GitDomain.createCommit(revCommit);
            commit.setRepository(repository.getDirectory().getName());
            result.add(commit);
        }

        return result;
    }

    private static final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
    private static final TimeZone tz = TimeZone.getTimeZone("PST"); // FIXME

    private List<ScmSummary> createEmptySummaries(int numDays) {
        List<ScmSummary> result = new ArrayList<ScmSummary>(numDays);

        Calendar cal = Calendar.getInstance(tz);
        cal.setTime(new Date());
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 1);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        Long now = cal.getTimeInMillis();
        for (int i = numDays - 1; i >= 0; i--) {
            ScmSummary summary = new ScmSummary();

            summary.setDate(new Date(now - i * MILLISECONDS_PER_DAY));
            summary.setAmount(0l);
            result.add(summary);
        }
        return result;
    }

    public interface CommitVisitor {
        void visit(RevCommit commit);
    }

    private void visitAllCommitsAfter(Date maxDate, CommitVisitor visitor) {
        Set<ObjectId> visited = new java.util.HashSet<ObjectId>();

        for (Repository repo : repositoryProvider.getAllRepositories()) {
            visitAllCommitsAfter(repo, maxDate, visitor, visited);
            repo.close();
        }
    }

    private void visitAllCommitsAfter(Repository repository, Date maxDate, CommitVisitor visitor,
            Set<ObjectId> visited) {
        try {

            for (Entry<String, Ref> entry : repository.getAllRefs().entrySet()) {
                if (entry.getValue().getName().startsWith(Constants.R_HEADS)) {
                    RevWalk revWal = new RevWalk(repository);
                    revWal.markStart(revWal.parseCommit(entry.getValue().getObjectId()));

                    int commitsPastDate = 0;
                    for (RevCommit revCommit : revWal) {
                        if (revCommit.getCommitterIdent().getWhen().getTime() < maxDate.getTime()) {
                            commitsPastDate++;
                        } else {
                            commitsPastDate = 0;
                            if (visited.add(revCommit)) {
                                visitor.visit(revCommit);
                            } else {
                                break;
                            }
                        }
                        if (commitsPastDate > 5) {
                            break;
                        }

                    }
                    revWal.dispose();

                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private List<RevCommit> getAllCommits(Repository repository, Region region, Set<ObjectId> visited) {
        TreeSet<RevCommit> result = new TreeSet<RevCommit>(new Comparator<RevCommit>() {

            @Override
            public int compare(RevCommit o1, RevCommit o2) {
                int ctDiff = o2.getCommitTime() - o1.getCommitTime();
                if (ctDiff != 0) {
                    return ctDiff;
                }
                return o1.getId().compareTo(o2.getId());
            }
        });

        int maxResultsToConsider = -1;
        if (region != null) {
            maxResultsToConsider = region.getOffset() + region.getSize();
        }
        long minTime = -1;

        try {

            for (Ref ref : getRefsToAdd(repository)) {
                RevWalk revWal = new RevWalk(repository);
                revWal.markStart(revWal.parseCommit(ref.getObjectId()));

                int index = 0;
                for (RevCommit revCommit : revWal) {
                    if (region == null
                            || (index >= region.getOffset() && index < region.getOffset() + region.getSize())) {
                        if (minTime > 0 && revCommit.getCommitTime() < minTime) {
                            break;
                        }
                        if (visited.add(revCommit.getId())) {
                            result.add(revCommit);

                            if (maxResultsToConsider > 0 && result.size() > maxResultsToConsider) {
                                RevCommit last = result.last();
                                result.remove(last);
                                minTime = last.getCommitTime();
                            }
                        } else {
                            break; // Done with this branch
                        }
                    }
                    index++;
                    if (region != null && (index >= region.getOffset() + region.getSize())) {
                        break;
                    }

                }

            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return new ArrayList<RevCommit>(result);
    }

    private List<Ref> getRefsToAdd(Repository repository) {
        List<Ref> result = new ArrayList<Ref>();
        Ref master = null;
        for (Entry<String, Ref> entry : repository.getAllRefs().entrySet()) {
            if (entry.getValue().getName().equals(Constants.R_HEADS + Constants.MASTER)) {
                master = entry.getValue();
            } else if (entry.getValue().getName().startsWith(Constants.R_HEADS)) {
                result.add(entry.getValue());
            }
        }
        if (master != null) {
            result.add(0, master);
        }
        return result;
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public List<Commit> getLog(Region region) {
        List<Commit> result = new ArrayList<Commit>();
        Set<ObjectId> visited = new HashSet<ObjectId>();

        for (Repository repo : repositoryProvider.getAllRepositories()) {
            result.addAll(getLog(repo, region, visited));
            repo.close();
        }

        Collections.sort(result, new Comparator<Commit>() {

            @Override
            public int compare(Commit o1, Commit o2) {
                return o2.getDate().compareTo(o1.getDate());
            }
        });

        QueryUtil.applyRegionToList(result, region);

        return result;
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public List<Commit> getLog(String repoName, String revision, String path, Region region)
            throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            List<Commit> commits = GitBrowseUtil.getLog(r, revision, path, region);
            r.close();
            return commits;
        } catch (AmbiguousObjectException ex) {
            throw new EntityNotFoundException();
        } catch (IOException ex) {
            throw new EntityNotFoundException();
        } catch (URISyntaxException ex) {
            throw new EntityNotFoundException();
        }

    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public List<Commit> getLog(String repoName, Region region) throws EntityNotFoundException {
        try {
            Repository repo;
            repo = findRepositoryByName(repoName);
            Set<ObjectId> visited = new HashSet<ObjectId>();

            List<Commit> result = getLog(repo, region, visited);
            repo.close();

            Collections.sort(result, new Comparator<Commit>() {

                @Override
                public int compare(Commit o1, Commit o2) {
                    return o2.getDate().compareTo(o1.getDate());
                }
            });

            QueryUtil.applyRegionToList(result, region);

            return result;

        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public List<Commit> getLogForBranch(String repoName, String branchName, Region region)
            throws EntityNotFoundException {
        if (region == null) {
            region = new Region(0, 100);
        }
        try {
            Repository repo;
            repo = findRepositoryByName(repoName);

            RevWalk revWal = new RevWalk(repo);

            Ref branchRef = repo.getRef(branchName);
            if (branchRef == null) {
                throw new EntityNotFoundException();
            }
            revWal.markStart(revWal.parseCommit(branchRef.getObjectId()));

            Iterator<RevCommit> iterator = revWal.iterator();

            int current = 0;
            // Skip the first
            while (current < region.getOffset() && iterator.hasNext()) {
                current++;
                iterator.next();
            }
            int page = 0;

            List<Commit> result = new ArrayList<Commit>();
            while (iterator.hasNext() && page < region.getSize()) {
                RevCommit commit = iterator.next();
                result.add(GitDomain.createCommit(commit));
                page++;
            }

            return result;

        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    private Repository findRepositoryByName(String repositoryName)
            throws IOException, URISyntaxException, EntityNotFoundException {
        for (ScmRepository repo : getHostedRepositories()) {
            if (repo.getName().equals(repositoryName)) {
                return repositoryProvider.getHostedRepository(repositoryName);
            }
        }
        // FIXME potential that an external repo is shadowned by the internal one
        for (ScmRepository repo : getExternalRepositories()) {
            if (repo.getName().equals(repositoryName)) {
                return repositoryProvider.getMirroredRepository(repositoryName);
            }
        }
        throw new EntityNotFoundException();
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public List<ScmSummary> getScmSummary(int numDays) {
        final List<ScmSummary> summary = createEmptySummaries(numDays);

        final Date firstDate = summary.get(0).getDate();

        CommitVisitor visitor = new CommitVisitor() {

            @Override
            public void visit(RevCommit revCommit) {
                Date commitDate = revCommit.getAuthorIdent().getWhen();
                if (commitDate.before(firstDate)) {
                    return;
                }
                for (int i = 0; i < summary.size(); i++) {
                    if (i == summary.size() - 1) {
                        summary.get(i).setAmount(summary.get(i).getAmount() + 1);
                    } else if (summary.get(i).getDate().before(commitDate)
                            && commitDate.before(summary.get(i + 1).getDate())) {
                        summary.get(i).setAmount(summary.get(i).getAmount() + 1);
                        break;
                    }
                }

            }
        };

        visitAllCommitsAfter(firstDate, visitor);
        return summary;
    }

    @Secured({ Role.Admin })
    @Override
    public void createEmptyRepository(ScmRepository repository) {
        File hostedDir = repositoryProvider.getTenantHostedBaseDir();
        File gitDir = new File(hostedDir, repository.getName());
        File descriptionFile = new File(gitDir, "description");
        gitDir.mkdirs();
        try {
            FileRepository repo = new FileRepository(gitDir);
            repo.create();
            descriptionFile.createNewFile();
            writeToDescription(descriptionFile, repository.getDescription());
            StoredConfig config = repo.getConfig();
            config.setBoolean("receive", null, "denynonfastforwards", true);
            config.save();
            repo.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Secured({ Role.Admin })
    @Override
    public void createEmptyRepository(String name) {
        ScmRepository repository = new ScmRepository();
        repository.setName(name);
        createEmptyRepository(repository);
    }

    private void writeToDescription(File descriptionFile, String description) throws IOException {
        org.apache.commons.io.FileUtils.writeStringToFile(descriptionFile, description);
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public List<ScmRepository> listRepositories() {
        List<ScmRepository> result = new ArrayList<ScmRepository>();
        try {
            result.addAll(getHostedRepositories());
            result.addAll(getExternalRepositories());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        Collections.sort(result, new Comparator<ScmRepository>() {

            @Override
            public int compare(ScmRepository repo1, ScmRepository repo2) {
                return repo1.getName().compareToIgnoreCase(repo2.getName());
            }
        });
        return result;
    }

    protected List<ScmRepository> getHostedRepositories() throws IOException {

        List<ScmRepository> result = new ArrayList<ScmRepository>();

        File hostedDir = repositoryProvider.getTenantHostedBaseDir();
        if (hostedDir != null && hostedDir.exists()) {

            for (String repoName : hostedDir.list()) {
                // if the fileName starts with a '.' then it's not a git repo
                if (!repoName.startsWith(".")) {
                    ScmRepository repo = new ScmRepository();
                    repo.setName(repoName);
                    repo.setScmLocation(ScmLocation.CODE2CLOUD);
                    repo.setType(ScmType.GIT);
                    repo.setUrl(profileServiceConfiguration.getHostedScmUrlPrefix(
                            TenancyUtil.getCurrentTenantProjectIdentifer()) + repo.getName()); // FIXME
                    repo.setAlternateUrl(computeSshUrl(repo));
                    File descriptionFile = new File(hostedDir.getPath() + File.separator + repoName, "description");
                    if (descriptionFile.exists()) {
                        String description = org.apache.commons.io.FileUtils.readFileToString(descriptionFile);
                        repo.setDescription(description);
                    }
                    result.add(repo);

                    Git git = Git.open(new File(hostedDir, repoName));

                    setBranchesAndTags(repo, git);
                }
            }
        }

        return result;
    }

    /**
     * @param repo
     * @param git
     */
    private void setBranchesAndTags(ScmRepository repo, Git git) {
        repo.setBranches(new ArrayList<String>());
        try {
            for (Ref ref : git.branchList().call()) {
                String refName = ref.getName();
                if (refName.startsWith(Constants.R_HEADS)) {
                    refName = refName.substring(Constants.R_HEADS.length());
                }
                repo.getBranches().add(refName);
            }
        } catch (GitAPIException e) {
            throw new JGitInternalException(e.getMessage(), e);
        }

        RevWalk revWalk = new RevWalk(git.getRepository());
        Map<String, Ref> refList;
        repo.setTags(new ArrayList<String>());
        try {
            refList = git.getRepository().getRefDatabase().getRefs(Constants.R_TAGS);
            for (Ref ref : refList.values()) {
                repo.getTags().add(ref.getName().substring(Constants.R_TAGS.length()));
            }
        } catch (IOException e) {
            throw new JGitInternalException(e.getMessage(), e);
        } finally {
            revWalk.release();
        }
        Collections.sort(repo.getTags());

    }

    private String computeSshUrl(ScmRepository repo) {
        String sshUriPath = getSshUriPath(repo);
        int sshPort = profileServiceConfiguration.getPublicSshPort();
        String webHost = profileServiceConfiguration.getWebHost();
        if (sshUriPath != null) {
            String url = "ssh://" + webHost;
            if (sshPort != 22) {
                url += ":" + sshPort;
            }
            url += sshUriPath;
            return url;
        }
        return null;
    }

    /**
     * Get an SSH URI path for this SCM repository for repositories that are hosted at Code2Cloud. The URI path is the
     * portion that follows the hostname and port in the URL.
     * 
     * @return the URI path, or null if it has none
     */
    private String getSshUriPath(ScmRepository repo) {
        String url = repo.getUrl();
        if (repo.getType() == ScmType.GIT && repo.getScmLocation() == ScmLocation.CODE2CLOUD && url != null) {
            int lastIndex = url.lastIndexOf('/');
            if (lastIndex != -1 && lastIndex < url.length() - 1) {
                return '/' + TenancyUtil.getCurrentTenantProjectIdentifer() + '/' + url.substring(lastIndex + 1);
            }
        }
        return null;
    }

    private List<ScmRepository> getExternalRepositories() throws IOException, URISyntaxException {
        List<ScmRepository> result = new ArrayList<ScmRepository>();

        File hostedDir = repositoryProvider.getTenantMirroredBaseDir();
        if (hostedDir != null && hostedDir.exists()) {
            for (String repoName : hostedDir.list()) {
                ScmRepository repo = new ScmRepository();
                repo.setName(repoName);
                repo.setScmLocation(ScmLocation.EXTERNAL);
                repo.setType(ScmType.GIT);

                Git git = Git.open(new File(hostedDir, repoName));
                RemoteConfig config = new RemoteConfig(git.getRepository().getConfig(),
                        Constants.DEFAULT_REMOTE_NAME);
                repo.setUrl(config.getURIs().get(0).toString());

                setBranchesAndTags(repo, git);

                result.add(repo);
            }
        }

        return result;
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public Map<Profile, Integer> getNumCommitsByAuthor(int numDays) {
        final Map<Profile, Integer> result = new HashMap<Profile, Integer>();
        final Map<String, Profile> profilesByName = new HashMap<String, Profile>();

        Date firstDay = new Date(System.currentTimeMillis() - MILLISECONDS_PER_DAY * numDays);

        CommitVisitor visitor = new CommitVisitor() {

            @Override
            public void visit(RevCommit c) {
                Profile p = profilesByName.get(c.getAuthorIdent().getName());
                if (p == null) {
                    p = GitDomain.fromPersonIdent(c.getAuthorIdent());
                    p.setId((long) profilesByName.size()); // wrong id but need for eq/hash
                    profilesByName.put(c.getAuthorIdent().getName(), p);
                }

                Integer count = result.get(p);
                if (count == null) {
                    count = 0;
                }
                count++;
                result.put(p, count);

            }
        };

        visitAllCommitsAfter(firstDay, visitor);

        return result;
    }

    static String getRepoDirNameFromExternalUrl(String url) {
        String path;
        try {
            path = new URI(url).getPath();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }

        int i = path.lastIndexOf("/");
        if (i == -1) {
            return path;
        }
        return path.substring(i + 1);
    }

    @Secured({ Role.Admin })
    @Override
    public void addExternalRepository(String url) {
        try {
            String repoName = getRepoDirNameFromExternalUrl(url);
            File dir = new File(repositoryProvider.getTenantMirroredBaseDir(), repoName);
            Git git = Git.init().setBare(true).setDirectory(dir).call();
            RemoteConfig config = new RemoteConfig(git.getRepository().getConfig(), Constants.DEFAULT_REMOTE_NAME);
            config.addURI(new URIish(url));
            config.update(git.getRepository().getConfig());
            git.getRepository().getConfig().save();
        } catch (JGitInternalException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (GitAPIException e) {
            throw new RuntimeException(e);
        }

    }

    @Secured({ Role.Admin })
    @Override
    public void removeExternalRepository(String url) {
        String repoName = getRepoDirNameFromExternalUrl(url);
        File dir = new File(repositoryProvider.getTenantMirroredBaseDir(), repoName);
        try {
            FileUtils.delete(dir, FileUtils.RECURSIVE);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (startThreads) {
            new FetchWorkerThread(this.repositoryProvider).start();
        }
    }

    @Secured({ Role.Admin })
    @Override
    public void removeInternalRepository(String name) {
        File hostedDir = repositoryProvider.getTenantHostedBaseDir();
        File dir = new File(hostedDir, name);
        try {
            FileUtils.delete(dir, FileUtils.RECURSIVE);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Commit getCommitWithDiff(String repositoryName, String commitId, Integer context)
            throws EntityNotFoundException {
        try {
            Repository jgitRepo = findRepositoryByName(repositoryName);

            if (context != null && context == -2 && commitId.length() <= 40 && AbbreviatedObjectId.isId(commitId)) {
                return GitBrowseUtil.resolveCommit(jgitRepo, commitId);
            }

            return getCommitInternal(repositoryName, jgitRepo, commitId, context);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (GitAPIException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<com.tasktop.c2c.server.scm.domain.DiffEntry> getDiffEntries(final String repositoryName,
            final String baseCommitId, final String commitId, final Integer context)
            throws EntityNotFoundException {
        try {
            final Repository jgitRepo = findRepositoryByName(repositoryName);
            final ObjectId commitObjectId = jgitRepo.resolve(commitId);
            final ObjectId baseCommitObjectId = baseCommitId != null ? jgitRepo.resolve(baseCommitId) : null;

            if (commitObjectId == null) {
                throw new EntityNotFoundException();
            }

            final RevWalk walk = new RevWalk(jgitRepo);
            final RevCommit baseCommit = baseCommitId != null ? walk.parseCommit(baseCommitObjectId) : null;
            return getDiffEntries(jgitRepo, baseCommit, walk.parseCommit(commitObjectId), context);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    private Commit getCommitInternal(String repositoryName, Repository repo, String commitId, Integer context)
            throws IOException, GitAPIException, EntityNotFoundException {

        ObjectId thisC = repo.resolve(commitId);

        if (thisC == null) {
            throw new EntityNotFoundException();
        }

        RevWalk walk = new RevWalk(repo);
        RevCommit theCommit = walk.parseCommit(thisC);

        Commit result = GitDomain.createCommit(theCommit);
        result.setUrl(profileServiceConfiguration.getWebUrlForCommit(TenancyUtil.getCurrentTenantProjectIdentifer(),
                result));
        result.setRepository(repositoryName);
        result.setChanges(getDiffEntries(repo, null/* lookup parent */, theCommit, context));

        return result;
    }

    @Secured({ Role.User })
    @Override
    public String createBranch(String repoName, String branchName) throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Git git = Git.wrap(r);
            CreateBranchCommand command = git.branchCreate();
            command.setName(branchName);

            /*
             * command.setStartPoint(getStartPoint().name()); if (upstreamMode != null)
             * command.setUpstreamMode(upstreamMode); command.call();
             */

            command.call();
            r.close();
            return branchName;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (GitAPIException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Secured({ Role.User })
    @Override
    public void deleteBranch(String repoName, String branchName) throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Git git = Git.wrap(r);
            DeleteBranchCommand command = git.branchDelete();
            command.setBranchNames(branchName);
            command.setForce(true);
            command.call();
            r.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (GitAPIException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public Trees getTrees(String repoName, String revision, String path, boolean history, int recursion)
            throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Trees trees = GitBrowseUtil.getTrees(r, revision, path, history, recursion);
            r.close();
            return trees;
        } catch (AmbiguousObjectException ex) {
            throw new EntityNotFoundException();
        } catch (IOException ex) {
            throw new EntityNotFoundException();
        } catch (URISyntaxException ex) {
            throw new EntityNotFoundException();
        } catch (GitAPIException ex) {
            throw new EntityNotFoundException();
        }
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public Blob getBlob(String repoName, String revision, String path) throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Blob b = GitBrowseUtil.getBlob(r, revision, path);
            r.close();
            return b;
        } catch (AmbiguousObjectException ex) {
            throw new EntityNotFoundException();
        } catch (IOException ex) {
            throw new EntityNotFoundException();
        } catch (URISyntaxException ex) {
            throw new EntityNotFoundException();
        }
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public Blame getBlame(String repoName, String revision, String path) throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Blame b = GitBrowseUtil.getBlame(r, revision, path);
            r.close();
            return b;
        } catch (AmbiguousObjectException ex) {
            throw new EntityNotFoundException();
        } catch (IOException ex) {
            throw new EntityNotFoundException();
        } catch (URISyntaxException ex) {
            throw new EntityNotFoundException();
        } catch (GitAPIException e) {
            throw new EntityNotFoundException();
        }
    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public Commit getMergeBase(String repoName, String revA, String revB) throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Commit c = GitBrowseUtil.getMergeBase(r, revA, revB);
            r.close();
            return c;
        } catch (IOException ex) {
            throw new EntityNotFoundException(ex.getMessage());
        } catch (URISyntaxException ex) {
            throw new EntityNotFoundException(ex.getMessage());
        }

    }

    @Secured({ Role.Observer, Role.User })
    @Override
    public Item getItem(String repoName, String revision, String path) throws EntityNotFoundException {
        try {
            Repository r = findRepositoryByName(repoName);
            Item i = GitBrowseUtil.getItem(r, revision, path);
            r.close();
            return i;
        } catch (AmbiguousObjectException ex) {
            throw new EntityNotFoundException();
        } catch (IOException ex) {
            throw new EntityNotFoundException();
        } catch (URISyntaxException ex) {
            throw new EntityNotFoundException();
        }
    }

    @Override
    public String getPublicSshKey() {
        File file = new File(repositoryProvider.getTenantBaseDir(), ".ssh/id_rsa.pub");
        if (!file.exists()) {
            return "";
        }

        try {
            return org.apache.commons.io.FileUtils.readFileToString(file);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private static List<com.tasktop.c2c.server.scm.domain.DiffEntry> getDiffEntries(Repository repository,
            RevCommit baseCommit, RevCommit commit, Integer context)
            throws EntityNotFoundException, MissingObjectException, IncorrectObjectTypeException, IOException {
        return getDiffEntries(repository, baseCommit, commit, RawTextComparator.DEFAULT, context);
    }

    private static List<com.tasktop.c2c.server.scm.domain.DiffEntry> getDiffEntries(final Repository repo,
            final RevCommit baseCommit, final RevCommit commit, final RawTextComparator cmp, final Integer context)
            throws IOException {
        final DiffFormatter df = new DiffFormatter(NullOutputStream.INSTANCE);
        df.setRepository(repo);
        df.setDiffComparator(cmp);
        df.setDetectRenames(true);

        RevTree baseTree = null;
        if (baseCommit != null) {
            baseTree = baseCommit.getTree();
        } else {
            RevCommit parentCommit = getParentCommit(repo, commit);
            if (parentCommit != null) {
                baseTree = parentCommit.getTree();
            }
        }
        final List<com.tasktop.c2c.server.scm.domain.DiffEntry> retval = new ArrayList<com.tasktop.c2c.server.scm.domain.DiffEntry>();
        for (DiffEntry de : df.scan(getTreeIterator(repo, baseTree), getTreeIterator(repo, commit.getTree()))) {
            retval.add(GitBrowseUtil.getDiffEntry(de, repo, context));
        }
        return retval;
    }

    private static RevCommit getParentCommit(final Repository repo, final RevCommit commit) throws IOException {
        RevCommit retval = null;
        if (commit.getParentCount() > 0) {
            final RevWalk rw = new RevWalk(repo);
            retval = rw.parseCommit(commit.getParent(0).getId());
            rw.dispose();
        }
        return retval;
    }

    private static AbstractTreeIterator getTreeIterator(final Repository repo, final RevTree tree)
            throws IOException {
        final CanonicalTreeParser treeParser = new CanonicalTreeParser();
        if (tree != null) {
            treeParser.reset(repo.newObjectReader(), tree);
        }
        return treeParser;
    }

    @Secured({ Role.Admin })
    @Override
    public void updateRepositoryDescription(ScmRepository repository) {
        File hostedDir = repositoryProvider.getTenantHostedBaseDir();
        File descriptionFile = new File(hostedDir + File.separator + repository.getName(), "description");
        try {
            writeToDescription(descriptionFile, repository.getDescription());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}