001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.connector.git; 017 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.util.Collections; 021import java.util.List; 022import org.eclipse.jgit.api.Git; 023import org.eclipse.jgit.api.errors.GitAPIException; 024import org.eclipse.jgit.diff.DiffEntry; 025import org.eclipse.jgit.diff.DiffFormatter; 026import org.eclipse.jgit.errors.CorruptObjectException; 027import org.eclipse.jgit.errors.IncorrectObjectTypeException; 028import org.eclipse.jgit.errors.MissingObjectException; 029import org.eclipse.jgit.lib.ObjectId; 030import org.eclipse.jgit.lib.Repository; 031import org.eclipse.jgit.revwalk.RevCommit; 032import org.eclipse.jgit.revwalk.RevWalk; 033import org.eclipse.jgit.treewalk.TreeWalk; 034import org.modeshape.schematic.document.Document; 035import org.modeshape.jcr.spi.federation.DocumentWriter; 036import org.modeshape.jcr.spi.federation.PageKey; 037import org.modeshape.jcr.spi.federation.PageWriter; 038 039/** 040 * A {@link GitFunction} that returns the information about a particular commit. The structure of this area of the repository is 041 * as follows: 042 * 043 * <pre> 044 * /commit/{branchOrTagNameOrObjectId} 045 * </pre> 046 */ 047public class GitCommitDetails extends GitFunction implements PageableGitFunction { 048 049 /** 050 * The name of the character set that is used when building the patch difference for a commit. 051 */ 052 public static final String DIFF_CHARSET_NAME = "UTF-8"; 053 054 protected static final String NAME = "commit"; 055 protected static final String ID = "/commit"; 056 057 protected static Object referenceToCommit( ObjectId id, 058 Values values ) { 059 return values.referenceTo(ID + DELIMITER + id.getName()); 060 } 061 062 protected static Object[] referencesToCommits( ObjectId[] ids, 063 Values values ) { 064 int size = ids.length; 065 Object[] results = new Object[size]; 066 for (int i = 0; i != size; ++i) { 067 results[i] = referenceToCommit(ids[i], values); 068 } 069 return results; 070 } 071 072 public GitCommitDetails( GitConnector connector ) { 073 super(NAME, connector); 074 } 075 076 @Override 077 public Document execute( Repository repository, 078 Git git, 079 CallSpecification spec, 080 DocumentWriter writer, 081 Values values ) throws GitAPIException, IOException { 082 if (spec.parameterCount() == 0) { 083 // This is the top-level "/commit" node 084 writer.setPrimaryType(GitLexicon.DETAILS); 085 086 // Generate the child references to the branches, tags, and commits in the history ... 087 addBranchesAsChildren(git, spec, writer); 088 addTagsAsChildren(git, spec, writer); 089 addCommitsAsChildren(git, spec, writer, pageSize); 090 091 } else if (spec.parameterCount() == 1) { 092 // This is the top-level "/commit/{branchOrTagNameOrObjectId}" node 093 writer.setPrimaryType(GitLexicon.DETAILED_COMMIT); 094 095 // Add the properties describing this commit ... 096 RevWalk walker = new RevWalk(repository); 097 walker.setRetainBody(true); 098 try { 099 String branchOrTagOrCommitId = spec.parameter(0); 100 ObjectId objId = resolveBranchOrTagOrCommitId(repository, branchOrTagOrCommitId); 101 RevCommit commit = walker.parseCommit(objId); 102 writer.addProperty(GitLexicon.OBJECT_ID, objId.name()); 103 writer.addProperty(GitLexicon.AUTHOR, authorName(commit)); 104 writer.addProperty(GitLexicon.COMMITTER, commiterName(commit)); 105 writer.addProperty(GitLexicon.COMMITTED, values.dateFrom(commit.getCommitTime())); 106 writer.addProperty(GitLexicon.TITLE, commit.getShortMessage()); 107 writer.addProperty(GitLexicon.MESSAGE, commit.getFullMessage().trim());// removes trailing whitespace 108 writer.addProperty(GitLexicon.PARENTS, GitCommitDetails.referencesToCommits(commit.getParents(), values)); 109 writer.addProperty(GitLexicon.TREE, GitTree.referenceToTree(objId, objId.name(), values)); 110 111 // Compute the difference between the commit and it's parent(s), and generate the diff/patch file ... 112 List<DiffEntry> differences = computeDifferences(commit, walker, repository); 113 String patchFile = computePatch(differences, repository); 114 writer.addProperty(GitLexicon.DIFF, patchFile); 115 116 } finally { 117 walker.dispose(); 118 } 119 } else { 120 return null; 121 } 122 123 return writer.document(); 124 } 125 126 @Override 127 public boolean isPaged() { 128 return true; 129 } 130 131 @Override 132 public Document execute( Repository repository, 133 Git git, 134 CallSpecification spec, 135 PageWriter writer, 136 Values values, 137 PageKey pageKey ) throws GitAPIException, IOException { 138 if (spec.parameterCount() != 0) return null; 139 addCommitsAsPageOfChildren(git, repository, spec, writer, pageKey); 140 return writer.document(); 141 } 142 143 protected List<DiffEntry> computeDifferences( RevCommit commit, 144 RevWalk walker, 145 Repository repository ) 146 throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { 147 // Set up the tree walk to obtain the difference between the commit and it's parent(s) ... 148 TreeWalk tw = new TreeWalk(repository); 149 tw.setRecursive(true); 150 tw.addTree(commit.getTree()); 151 152 RevCommit[] parents = commit.getParents(); 153 154 for (RevCommit parent : parents) { 155 RevCommit parentCommit = walker.parseCommit(parent); 156 tw.addTree(parentCommit.getTree()); 157 //if there are multiple parents, we can't really have a multiple-way diff so we'll only look at the first parent 158 if (parents.length > 1) { 159 connector.getLogger().warn(GitI18n.commitWithMultipleParents, commit.getName(), parentCommit.getName()); 160 break; 161 } 162 } 163 164 if (tw.getTreeCount() == 1) { 165 connector.getLogger().warn(GitI18n.commitWithSingleParent, commit.getName(), tw.getObjectId(0).name()); 166 return Collections.emptyList(); 167 } 168 169 // Now process the diff of each file ... 170 return DiffEntry.scan(tw); 171 } 172 173 protected String computePatch( Iterable<DiffEntry> entries, 174 Repository repository ) throws IOException { 175 ByteArrayOutputStream output = new ByteArrayOutputStream(); 176 DiffFormatter formatter = new DiffFormatter(output); 177 formatter.setRepository(repository); 178 for (DiffEntry entry : entries) { 179 formatter.format(entry); 180 } 181 return output.toString(DIFF_CHARSET_NAME); 182 } 183 184}