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

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2010, 2012 Oracle and/or its affiliates
 *
 * 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:
 *     Oracle and/or its affiliates.
 ******************************************************************************/
package com.tasktop.c2c.server.scm.service;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.eclipse.jgit.api.BlameCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.blame.BlameResult;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.revwalk.filter.SkipRevFilter;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.io.NullOutputStream;

import com.tasktop.c2c.server.common.service.EntityNotFoundException;
import com.tasktop.c2c.server.common.service.domain.Region;
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.DiffEntry;
import com.tasktop.c2c.server.scm.domain.DiffEntry.Hunk;
import com.tasktop.c2c.server.scm.domain.Item;
import com.tasktop.c2c.server.scm.domain.Trees;

//import com.tasktop.c2c.server.scm.domain.DiffEntry.Content;

class GitBrowseUtil {

    // XXX implement recursion at least the -1
    public static Trees getTrees(Repository r, String revision, String path, boolean history, int recursion)
            throws IOException, GitAPIException, EntityNotFoundException {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        RevObject revId = resolveRevision(r, revision);

        MutableObjectId revTree = new MutableObjectId();
        TreeWalk treeWalk = getTreeOnPath(revTree, r, revId, path);

        if (treeWalk == null) {
            throw new EntityNotFoundException("Revision: " + revision + ", path: " + path + " not found.");
        }

        Trees trees = new Trees(revTree.name());

        Git git = history ? new Git(r) : null;

        while (treeWalk.next()) {
            ObjectLoader loader = r.open(treeWalk.getObjectId(0));
            Trees.Tree t = new Trees.Tree(treeWalk.getObjectId(0).getName(), // sha
                    treeWalk.getPathString(), // path
                    treeWalk.getNameString(), // name
                    getType(loader.getType()), // type
                    treeWalk.getRawMode(0), // mode
                    loader.getSize() // size
            );
            trees.getTree().add(t);

            if (recursion == -2 && t.getType() == Item.Type.TREE) {
                t.setEmpty(getEmptyPath(r, treeWalk.getObjectId(0)));
            }

            if (history) {
                addHistory(git, t, revId, /* path + ( path.length() == 0 ? "" : "/") + */t.getPath());
            }

        }

        return trees;
    }

    public static Blob getBlob(Repository r, String revision, String path)
            throws IOException, EntityNotFoundException {

        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        String id = resolve(r, r.resolve(revision), path);
        if (id == null) {
            throw new EntityNotFoundException();
        }
        ObjectId objectId = ObjectId.fromString(id);

        ObjectLoader loader = r.getObjectDatabase().open(objectId, Constants.OBJ_BLOB);

        Blob b = new Blob(id);

        if (loader.isLarge()) {
            b.setLarge(true);
            InputStream is = null;
            IOException ioex = null;
            try {
                is = loader.openStream();
                b.setBinary(RawText.isBinary(is));
            } catch (IOException ex) {
                ioex = ex;
            } finally {
                if (is != null) {
                    is.close();
                }
                if (ioex != null) {
                    throw ioex;
                }
            }

        } else {
            byte[] raw = loader.getBytes();

            boolean binary = RawText.isBinary(raw);

            if (binary) {
                b.setBinary(true);
                b.setLines(Collections.<String>emptyList());
            } else {

                RawText rt = new RawText(raw);
                List<String> lines = new ArrayList<String>(rt.size());

                for (int i = 0; i < rt.size(); i++) {
                    lines.add(rt.getString(i));
                }

                b.setLines(lines);
            }
        }

        return b;
    }

    public static Blame getBlame(Repository r, String revision, String path) throws IOException, GitAPIException {

        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        Git git = new Git(r);

        ObjectId revId = r.resolve(revision);

        BlameCommand bc = git.blame();
        bc.setStartCommit(revId);
        bc.setFilePath(path);
        BlameResult br = bc.call();

        Blame blame = new Blame();
        blame.path = path;
        blame.revision = revision;
        blame.commits = new ArrayList<Commit>();
        blame.lines = new ArrayList<Blame.Line>();

        Map<String, Integer> sha2idx = new HashMap<String, Integer>();

        RawText resultContents = br.getResultContents();

        for (int i = 0; i < br.getResultContents().size(); i++) {

            RevCommit sourceCommit = br.getSourceCommit(i); // XXX should it really be the source commit
            String sha = sourceCommit.getName();
            Integer cIdx = sha2idx.get(sha);
            if (cIdx == null) {
                cIdx = blame.commits.size();
                Commit commit = GitDomain.createCommit(sourceCommit);
                blame.commits.add(commit);
                sha2idx.put(sha, cIdx);
            }

            Blame.Line bl = new Blame.Line();
            bl.commit = cIdx;
            bl.text = resultContents.getString(i);
            blame.lines.add(bl);
        }

        return blame;
    }

    public static List<Commit> getLog(Repository r, String revision, String path, Region region)
            throws IOException {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        List<Commit> commits = new ArrayList<Commit>();

        ObjectId revId = r.resolve(revision);
        RevWalk walk = new RevWalk(r);

        walk.markStart(walk.parseCommit(revId));

        if (path != null && path.trim().length() != 0) {
            walk.setTreeFilter(AndTreeFilter.create(PathFilterGroup.createFromStrings(path), TreeFilter.ANY_DIFF));
        }

        if (region != null) {
            // if (region.getOffset() > 0 && region.getSize() > 0)
            // walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(region.getOffset()),
            // MaxCountRevFilter.create(region.getSize())));
            /* else */if (region.getOffset() > 0) {
                walk.setRevFilter(SkipRevFilter.create(region.getOffset()));
            }
            // else if (region.getSize() > 0) {
            // walk.setRevFilter(MaxCountRevFilter.create(region.getSize()));
            // }
        }

        int max = region != null && region.getSize() > 0 ? region.getSize() : -1;

        for (RevCommit revCommit : walk) {
            Commit commit = GitDomain.createCommit(revCommit);
            commit.setRepository(r.getDirectory().getName());
            commits.add(commit);
            if (max != -1) {
                if (max == 0) {
                    break;
                }
                max--;
            }
        }

        return commits;
    }

    public static Item getItem(Repository r, String revision, String path)
            throws IOException, EntityNotFoundException {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        String id = resolve(r, r.resolve(revision), path);
        if (id == null) {
            throw new EntityNotFoundException();
        }
        ObjectId objectId = ObjectId.fromString(id);

        ObjectLoader loader = r.open(objectId);

        return new Item(id, getType(loader.getType()));

    }

    public static Commit getMergeBase(Repository r, String revA, String revB) throws IOException {

        RevWalk walk = new RevWalk(r);

        RevCommit a = walk.parseCommit(r.resolve(revA));
        RevCommit b = walk.parseCommit(r.resolve(revB));

        RevCommit base = getBaseCommit(walk, a, b);

        return GitDomain.createCommit(base);
    }

    public static Commit resolveCommit(Repository r, String commitId) throws IOException {

        ObjectId oid = null;
        Commit commit = new Commit();

        if (commitId.length() < 40) {
            ObjectReader or = r.newObjectReader();
            try {
                AbbreviatedObjectId aid = AbbreviatedObjectId.fromString(commitId);

                Collection<ObjectId> oids = or.resolve(aid);

                if (oids == null || oids.size() == 0) {
                    commit.setSha("nex");
                    return commit;
                } else if (oids.size() > 1) {
                    commit.setSha("ambigous");
                    return commit;
                } else {
                    oid = oids.iterator().next();
                }
            } finally {
                or.release();
            }
        } else {
            oid = ObjectId.fromString(commitId);
        }

        RevWalk rw = new RevWalk(r);
        try {
            RevCommit rc = rw.parseCommit(oid);
            return GitDomain.createCommit(rc);
        } catch (MissingObjectException ex) {
            commit.setSha("nex");
            return commit;
        } catch (IncorrectObjectTypeException ex) {
            commit.setSha("nex");
            return commit;
        }

    }

    // Private methods ---------------------------------------------------------

    // Taken/modified from JGit 3.0 Merger class
    protected static RevCommit getBaseCommit(RevWalk walk, RevCommit a, RevCommit b)
            throws IncorrectObjectTypeException, IOException {
        walk.reset();
        walk.setRevFilter(RevFilter.MERGE_BASE);
        walk.markStart(a);
        walk.markStart(b);
        final RevCommit base = walk.next();
        if (base == null)
            return null;
        final RevCommit base2 = walk.next();
        if (base2 != null) {
            // throw new NoMergeBaseException(MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED,
            // MessageFormat.format(JGitText.get().multipleMergeBasesFor, a.name(), b.name(), base.name(),
            // base2.name()));
            throw new IOException(MessageFormat.format(JGitText.get().multipleMergeBasesFor, a.name(), b.name(),
                    base.name(), base2.name()));

        }
        return base;
    }

    private static TreeWalk getTreeOnPath(MutableObjectId id, Repository repo, ObjectId rev, String path)
            throws MissingObjectException, IOException {

        RevTree tree = new RevWalk(repo).parseTree(rev);

        TreeWalk tw = new TreeWalk(repo);
        tw.reset(tree);
        tw.setRecursive(false);

        if (path == null || path.isEmpty() || "/".equals(path)) {
            id.fromObjectId(tree.getId());
            return tw;
        }

        PathFilter f = PathFilter.create(path);
        tw.setFilter(f);

        while (tw.next()) {
            if (f.isDone(tw)) {
                id.fromObjectId(tw.getObjectId(0));
                if (tw.isSubtree()) {
                    tw.enterSubtree();
                    return tw;
                } else {
                    throw new MissingObjectException(tw.getObjectId(0), Constants.OBJ_TREE);
                }

            } else if (tw.isSubtree()) {
                tw.enterSubtree();
            }
        }
        return null;

    }

    public static String resolve(Repository r, ObjectId revision, String path)
            throws MissingObjectException, IncorrectObjectTypeException, IOException {

        if (revision == null) {
            return null;
        }

        RevCommit commit = new RevWalk(r).parseCommit(revision);

        if ("".equals(path)) {
            return commit.getTree().getName();
        }

        TreeWalk walk = TreeWalk.forPath(r, path, commit.getTree()); // new TreeWalk(r);
        // walk.reset();
        // walk.addTree(commit.getTree());
        // walk.setFilter(PathFilter.create(path));
        // walk.setRecursive(false);
        // while (walk.next()) {
        if (walk != null && path.equals(walk.getPathString())) {
            // WorkingTreeOptions opt = r.getConfig().get(WorkingTreeOptions.KEY);
            return walk.getObjectId(0).getName();
        }
        // }
        return null;

    }

    public static RevObject resolveRevision(Repository r, String revision)
            throws EntityNotFoundException, IOException {

        RevWalk rw = null;

        try {

            ObjectId oid = r.resolve(revision);

            if (oid == null) {
                throw new EntityNotFoundException("Revision: " + revision + " not found.");
            }

            rw = new RevWalk(r);

            RevObject ro = rw.parseCommit(oid);

            // if (ro.getType() == Constants.OBJ_TAG) {
            // return ((RevTag) ro).getObject();
            // }

            return ro;
        } finally {
            if (rw != null) {
                rw.release();
            }
        }

    }

    private static Item.Type getType(int type) {
        switch (type) {
        case Constants.OBJ_BLOB:
            return Item.Type.BLOB;
        case Constants.OBJ_TREE:
            return Item.Type.TREE;
        case Constants.OBJ_COMMIT:
            return Item.Type.COMMIT;
        // XXX add more
        default:
            return null;
        }
    }

    private static String getEmptyPath(Repository r, ObjectId id) throws IOException {
        TreeWalk treeWalk = new TreeWalk(r);
        treeWalk.addTree(new RevWalk(r).parseTree(id));

        String path = null;

        // Find tree of interrest it has to be alone there
        ObjectId iId = null;
        String iPath = null;
        while (treeWalk.next()) {
            if (iId == null) {
                iId = treeWalk.getObjectId(0);
                iPath = treeWalk.getPathString();
            } else {
                iId = null;
                break;
            }
        }

        if (iId != null) {
            ObjectLoader loader = r.open(iId);
            if (loader.getType() == Constants.OBJ_TREE) { // It is alone there and it is a folder
                path = "/" + iPath;
                String sep = getEmptyPath(r, iId);
                if (sep != null) {
                    path = path + sep;
                }
            }
        }

        return path;

    }

    private static void addHistory(Git git, Trees.Tree tree, ObjectId revision, String path)
            throws MissingObjectException, IncorrectObjectTypeException, GitAPIException {
        LogCommand logCommand = git.log();
        logCommand.add(revision);

        logCommand.addPath(path);

        for (RevCommit revCommit : logCommand.call()) {
            Commit commit = GitDomain.createCommit(revCommit);
            tree.setLatestCommit(commit);
            break;
        }

    }

    public static DiffEntry getDiffEntry(org.eclipse.jgit.diff.DiffEntry de, Repository repo, Integer context)
            throws IOException {
        return new StructuredDiffEntryFormatter(de, repo, context).getDiffEntry();
    }

    private static class StructuredDiffEntryFormatter extends DiffFormatter {

        private final org.eclipse.jgit.diff.DiffEntry de;

        private Stack<Hunk> hunks = new Stack<Hunk>();

        private boolean binary = false;
        private int linesAdded = 0;
        private int linesRemoved = 0;

        public StructuredDiffEntryFormatter(org.eclipse.jgit.diff.DiffEntry de, Repository repo, Integer context) {
            super(NullOutputStream.INSTANCE);
            this.de = de;
            setRepository(repo);
            if (context != null && context >= 0) {
                setContext(context);
            }
        }

        public DiffEntry getDiffEntry() throws IOException {

            format(de);

            DiffEntry result = new DiffEntry();

            result.setChangeType(convertChangeType(de.getChangeType()));
            result.setNewPath(de.getNewPath());
            result.setOldPath(de.getOldPath());
            result.setHunks(hunks);
            result.setLinesAdded(linesAdded);
            result.setLinesRemoved(linesRemoved);
            result.setBinary(binary);

            return result;
        }

        @Override
        public void format(FileHeader head, RawText a, RawText b) throws IOException {
            binary = head.getPatchType() != FileHeader.PatchType.UNIFIED;
            super.format(head, a, b); // To change body of generated methods, choose Tools | Templates.
        }

        @Override
        protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
                throws IOException {
            hunks.push(new Hunk(aStartLine, aEndLine, bStartLine, bEndLine));
        }

        @Override
        protected void writeContextLine(RawText text, int line) throws IOException {
            hunks.peek().getLineChanges()
                    .add(new Hunk.LineChange(Hunk.LineChange.Type.CONTEXT, text.getString(line)));
        }

        @Override
        protected void writeAddedLine(RawText text, int line) throws IOException {
            linesAdded++;
            hunks.peek().getLineChanges()
                    .add(new Hunk.LineChange(Hunk.LineChange.Type.ADDED, text.getString(line)));
        }

        @Override
        protected void writeRemovedLine(RawText text, int line) throws IOException {
            linesRemoved++;
            hunks.peek().getLineChanges()
                    .add(new Hunk.LineChange(Hunk.LineChange.Type.REMOVED, text.getString(line)));
        }

        private static DiffEntry.ChangeType convertChangeType(org.eclipse.jgit.diff.DiffEntry.ChangeType ct) {
            switch (ct) {
            case ADD:
                return DiffEntry.ChangeType.ADD;
            case COPY:
                return DiffEntry.ChangeType.COPY;
            case DELETE:
                return DiffEntry.ChangeType.DELETE;
            case MODIFY:
                return DiffEntry.ChangeType.MODIFY;
            case RENAME:
                return DiffEntry.ChangeType.RENAME;
            default:
                return null;
            }
        }

    }

}