/******************************************************************************* * Copyright (c) 2011, 2015 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.orion.server.git.objects; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand.ListMode; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.MissingObjectException; 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.revwalk.RevCommit; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.orion.server.core.ProtocolConstants; import org.eclipse.orion.server.core.resources.Property; import org.eclipse.orion.server.core.resources.ResourceShape; import org.eclipse.orion.server.core.resources.annotations.PropertyDescription; import org.eclipse.orion.server.core.resources.annotations.ResourceDescription; import org.eclipse.orion.server.git.BaseToCommitConverter; import org.eclipse.orion.server.git.GitConstants; import org.eclipse.orion.server.git.servlets.GitUtils; import org.eclipse.osgi.util.NLS; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * A compound object for {@link Commit}s. * */ @ResourceDescription(type = Commit.TYPE) public class Log extends GitObject { private static final ResourceShape DEFAULT_RESOURCE_SHAPE = new ResourceShape(); { Property[] defaultProperties = new Property[] { // new Property(ProtocolConstants.KEY_LOCATION), // super new Property(GitConstants.KEY_CLONE), // super new Property(ProtocolConstants.KEY_CHILDREN), // new Property(GitConstants.KEY_REPOSITORY_PATH), // new Property(GitConstants.KEY_LOG_TO_REF), // new Property(GitConstants.KEY_LOG_FROM_REF), // new Property(ProtocolConstants.KEY_PREVIOUS_LOCATION), // new Property(ProtocolConstants.KEY_NEXT_LOCATION) }; DEFAULT_RESOURCE_SHAPE.setProperties(defaultProperties); } private List<RevCommit> commits; private String pattern; private String messagePattern; private String authorPattern; private String committerPattern; private String sha1Pattern; private String fromDate; private String toDate; private boolean mergeBase; private Ref toRefId; private Ref fromRefId; private int page; private int pageSize; public Log(URI cloneLocation, Repository db, Iterable<RevCommit> commits, String pattern, Ref toRefId, Ref fromRefId) { super(cloneLocation, db); this.commits = commits != null ? toList(commits) : Collections.<RevCommit> emptyList(); this.pattern = pattern; this.toRefId = toRefId; this.fromRefId = fromRefId; } public void setCommits(Iterable<RevCommit> commits) { this.commits = toList(commits); } private static <E> List<E> toList(Iterable<E> iterable) { List<E> list = new ArrayList<E>(); for (E item : iterable) list.add(item); return list; } public void setPaging(int page, int pageSize) { this.page = page; this.pageSize = pageSize; } public void setMessagePattern(String messagePattern) { this.messagePattern = messagePattern; } public void setAuthorPattern(String authorPattern) { this.authorPattern = authorPattern; } public void setCommitterPattern(String committerPattern) { this.committerPattern = committerPattern; } public void setSHA1Pattern(String sha1Pattern) { this.sha1Pattern = sha1Pattern; } public void setFromDate(String fromDate) { this.fromDate = fromDate; } public void setToDate(String toDate) { this.toDate = toDate; } public void setMergeBaseFilter(boolean mergeBaseFilter) { this.mergeBase = mergeBaseFilter; } @Override public JSONObject toJSON() throws JSONException, URISyntaxException, IOException, CoreException { Assert.isNotNull(commits, "'commits' is null"); return jsonSerializer.serialize(this, DEFAULT_RESOURCE_SHAPE); } @PropertyDescription(name = ProtocolConstants.KEY_CHILDREN) private JSONArray getChildren() throws GitAPIException, JSONException, URISyntaxException, IOException, CoreException { Map<ObjectId, JSONArray> commitToBranchMap = getCommitToBranchMap(cloneLocation, db); Map<ObjectId, Map<String, Ref>> commitToTagMap = getCommitToTagMap(cloneLocation, db); JSONArray children = new JSONArray(); int i = 0; for (RevCommit revCommit : commits) { Commit commit = new Commit(cloneLocation, db, revCommit, pattern); commit.setCommitToBranchMap(commitToBranchMap); commit.setCommitToTagMap(commitToTagMap); children.put(commit.toJSON()); if (i++ == pageSize - 1) break; } return children; } @PropertyDescription(name = GitConstants.KEY_REPOSITORY_PATH) private String getRepositoryPath() { return pattern == null ? "" : pattern; //$NON-NLS-1$ } @PropertyDescription(name = GitConstants.KEY_LOG_TO_REF) private JSONObject getToRef() throws JSONException, URISyntaxException, IOException, CoreException { if (toRefId != null) return createJSONObjectForRef(toRefId.getTarget()); return null; } @PropertyDescription(name = GitConstants.KEY_LOG_FROM_REF) private JSONObject getFromRef() throws JSONException, URISyntaxException, IOException, CoreException { if (fromRefId != null) return createJSONObjectForRef(fromRefId.getTarget()); return null; } @PropertyDescription(name = ProtocolConstants.KEY_PREVIOUS_LOCATION) private URI getPreviousPageLocation() throws URISyntaxException { if (page > 0) { String c = getRefRange(); String q = getCommitQuery(); if (page > 1) { return BaseToCommitConverter.getCommitLocation(cloneLocation, GitUtils.encode(c), pattern, BaseToCommitConverter.REMOVE_FIRST_2.setQuery(String.format(q, page - 1, pageSize))); } } return null; } private String getRefRange() { StringBuilder c = new StringBuilder(""); //$NON-NLS-1$ if (fromRefId != null) c.append(fromRefId.getName()); if (fromRefId != null && toRefId != null) c.append(".."); //$NON-NLS-1$ if (toRefId != null) c.append(Repository.shortenRefName(toRefId.getName())); return c.toString(); } private String getCommitQuery() { String q = "page=%d&pageSize=%d"; //$NON-NLS-1$ if (this.messagePattern != null) { q += "&filter=" + GitUtils.encode(this.messagePattern); //$NON-NLS-1$ } if (this.authorPattern != null) { q += "&author=" + GitUtils.encode(this.authorPattern); //$NON-NLS-1$ } if (this.committerPattern != null) { q += "&committer=" + GitUtils.encode(this.committerPattern); //$NON-NLS-1$ } if (this.sha1Pattern != null) { q += "&sha1=" + GitUtils.encode(this.sha1Pattern); //$NON-NLS-1$ } if (this.fromDate != null) { q += "&fromDate=" + this.fromDate; //$NON-NLS-1$ } if (this.toDate != null) { q += "&toDate=" + this.toDate; //$NON-NLS-1$ } if (this.mergeBase) { q += "&mergeBase=true"; //$NON-NLS-1$ } return q; } @PropertyDescription(name = ProtocolConstants.KEY_NEXT_LOCATION) private URI getNextPageLocation() throws URISyntaxException { if (hasNextPage()) { String c = getRefRange(); String q = getCommitQuery(); return BaseToCommitConverter.getCommitLocation(cloneLocation, GitUtils.encode(c), pattern, BaseToCommitConverter.REMOVE_FIRST_2.setQuery(String.format(q, page + 1, pageSize))); } return null; } private boolean hasNextPage() { return commits.size() > pageSize; } private JSONObject createJSONObjectForRef(Ref targetRef) throws JSONException, URISyntaxException, IOException, CoreException { JSONObject result = null; String name = targetRef.getName(); if (name.startsWith(Constants.R_HEADS)) { result = new Branch(cloneLocation, db, targetRef).toJSON(); } else if (name.startsWith(Constants.R_REMOTES)) { Remote remote = findRemote(name); String remoteBranchName = computeRemoteBranchName(name, remote); result = new RemoteBranch(cloneLocation, db, remote, remoteBranchName).toJSON(); } // Assert.isNotNull(result, NLS.bind("Unexpected target Ref: {0}", name)); return result; } private Remote findRemote(String refName) throws URISyntaxException { Assert.isLegal(refName.startsWith(Constants.R_REMOTES), NLS.bind("Expected Ref starting with {0} was {1}", Constants.R_REMOTES, refName)); IPath remoteNameCandidate = new Path(refName).removeFirstSegments(2); List<RemoteConfig> remoteConfigs = RemoteConfig.getAllRemoteConfigs(getConfig()); for (int i = 1; i < remoteNameCandidate.segmentCount(); i++) { for (RemoteConfig remoteConfig : remoteConfigs) { IPath uptoSegment = remoteNameCandidate.uptoSegment(i); if (remoteConfig.getName().equals(uptoSegment.toString())) return new Remote(cloneLocation, db, remoteConfig.getName()); } } Assert.isTrue(false, NLS.bind("Could not find Remote for {0}", refName)); return null; } private String computeRemoteBranchName(String targetRefName, Remote remote) { String prefix = Constants.R_REMOTES + remote.getName() + "/"; //$NON-NLS-1$ return targetRefName.substring(prefix.length()); } @Override protected URI getLocation() throws URISyntaxException { StringBuilder c = new StringBuilder(); if (fromRefId != null) c.append(fromRefId.getName()); if (fromRefId != null && toRefId != null) c.append(".."); //$NON-NLS-1$ if (toRefId != null) c.append(toRefId.getName()); // TODO: lost paging info return BaseToCommitConverter.getCommitLocation(cloneLocation, GitUtils.encode(c.toString()), pattern, BaseToCommitConverter.REMOVE_FIRST_2); } static Map<ObjectId, JSONArray> getCommitToBranchMap(URI cloneLocation, Repository db) throws GitAPIException, JSONException { HashMap<ObjectId, JSONArray> commitToBranch = new HashMap<ObjectId, JSONArray>(); Git git = Git.wrap(db); List<Ref> branchRefs = git.branchList().setListMode(ListMode.ALL).call(); for (Ref branchRef : branchRefs) { ObjectId commitId = branchRef.getLeaf().getObjectId(); JSONObject branch = new JSONObject(); branch.put(ProtocolConstants.KEY_FULL_NAME, branchRef.getName()); JSONArray branchesArray = commitToBranch.get(commitId); if (branchesArray != null) { branchesArray.put(branch); } else { branchesArray = new JSONArray(); branchesArray.put(branch); commitToBranch.put(commitId, branchesArray); } } return commitToBranch; } static Map<ObjectId, Map<String, Ref>> getCommitToTagMap(URI cloneLocation, Repository db) throws MissingObjectException, IOException { HashMap<ObjectId, Map<String, Ref>> commitToTag = new HashMap<ObjectId, Map<String, Ref>>(); for (Entry<String, Ref> tag : db.getTags().entrySet()) { Ref ref = db.peel(tag.getValue()); ObjectId commitId = ref.getPeeledObjectId(); if (commitId == null) commitId = ref.getObjectId(); Map<String, Ref> tags = commitToTag.get(commitId); if (tags != null) { tags.put(tag.getKey(), tag.getValue()); } else { tags = new HashMap<String, Ref>(); tags.put(tag.getKey(), tag.getValue()); commitToTag.put(commitId, tags); } } return commitToTag; } }