blob: 772e983f439ed1ea9668fc5d1421de2abab69b47 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.libs.git;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
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.TreeFilter;
import org.netbeans.libs.git.jgit.Utils;
/**
* Provides information about a certain commit, usually is returned by
* git commit or log command.
*
* @author Jan Becicka
*/
public final class GitRevisionInfo {
private final RevCommit revCommit;
private final Repository repository;
private final Map<String, GitBranch> branches;
private GitFileInfo[] modifiedFiles;
private static final Logger LOG = Logger.getLogger(GitRevisionInfo.class.getName());
private String shortMessage;
GitRevisionInfo (RevCommit commit, Repository repository) {
this(commit, Collections.<String, GitBranch>emptyMap(), repository);
}
GitRevisionInfo (RevCommit commit, Map<String, GitBranch> affectedBranches, Repository repository) {
this.revCommit = commit;
this.repository = repository;
this.branches = Collections.unmodifiableMap(affectedBranches);
}
/**
* @return id of the commit
*/
public String getRevision () {
return ObjectId.toString(revCommit.getId());
}
/**
* @return the first line of the commit message.
*/
public String getShortMessage () {
if (shortMessage == null) {
String msg = revCommit.getFullMessage();
StringBuilder sb = new StringBuilder();
boolean empty = true;
for (int pos = 0; pos < msg.length(); ++pos) {
char c = msg.charAt(pos);
if (c == '\r' || c == '\n') {
if (!empty) {
break;
}
} else {
sb.append(c);
empty = false;
}
}
shortMessage = sb.toString();
}
return shortMessage;
}
/**
* @return full commit message
*/
public String getFullMessage () {
return revCommit.getFullMessage();
}
/**
* @return time this commit was created in milliseconds.
*/
public long getCommitTime () {
// must be indeed author, that complies with CLI
// committer time is different after rebase
PersonIdent author = revCommit.getAuthorIdent();
if (author == null) {
return (long) revCommit.getCommitTime() * 1000;
} else {
return author.getWhen().getTime();
}
}
/**
* @return author of the commit
*/
public GitUser getAuthor () {
return GitClassFactoryImpl.getInstance().createUser(revCommit.getAuthorIdent());
}
/**
* @return person who actually committed the changes, may or may not be the same as a return value of the <code>getAuthor</code> method.
*/
public GitUser getCommitter () {
return GitClassFactoryImpl.getInstance().createUser(revCommit.getCommitterIdent());
}
/**
* Returns the information about the files affected (modified, deleted or added) by this commit.
* <strong>First time call should not be done from the EDT.</strong> When called for the first time the method execution can take a big amount of time
* because it compares the commit tree with its parents and identifies the modified files.
* Any subsequent call to the first <strong>successful</strong> call will return the cached value and will be fast.
* @return files affected by this change set
* @throws GitException when an error occurs
*/
public java.util.Map<java.io.File, GitFileInfo> getModifiedFiles () throws GitException {
if (modifiedFiles == null) {
synchronized (this) {
listFiles();
}
}
Map<File, GitFileInfo> files = new HashMap<>(modifiedFiles.length);
for (GitFileInfo info : modifiedFiles) {
files.put(info.getFile(), info);
}
return files;
}
/**
* @return commit ids of this commit's parents
*/
public String[] getParents () {
String[] parents = new String[revCommit.getParentCount()];
for (int i = 0; i < revCommit.getParentCount(); ++i) {
parents[i] = ObjectId.toString(revCommit.getParent(i).getId());
}
return parents;
}
/**
* @return all branches known to contain this commit.
* @since 1.14
*/
public Map<String, GitBranch> getBranches () {
return branches;
}
private void listFiles() throws GitException {
try (RevWalk revWalk = new RevWalk(repository);
TreeWalk walk = new TreeWalk(repository)) {
List<GitFileInfo> result;
walk.reset();
walk.setRecursive(true);
RevCommit parentCommit = null;
if (revCommit.getParentCount() > 0) {
for (RevCommit commit : revCommit.getParents()) {
revWalk.markStart(revWalk.lookupCommit(commit));
}
revWalk.setRevFilter(RevFilter.MERGE_BASE);
Iterator<RevCommit> it = revWalk.iterator();
if (it.hasNext()) {
parentCommit = it.next();
}
if (parentCommit != null) {
walk.addTree(parentCommit.getTree().getId());
}
}
walk.addTree(revCommit.getTree().getId());
walk.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, PathFilter.ANY_DIFF));
if (parentCommit != null) {
result = Utils.getDiffEntries(repository, walk, GitClassFactoryImpl.getInstance());
} else {
result = new ArrayList<>();
while (walk.next()) {
result.add(new GitFileInfo(new File(repository.getWorkTree(), walk.getPathString()), walk.getPathString(), GitFileInfo.Status.ADDED, null, null));
}
}
this.modifiedFiles = result.toArray(new GitFileInfo[result.size()]);
} catch (IOException ex) {
throw new GitException(ex);
}
}
private static Map<String, GitBranch> buildBranches (RevCommit commit, Map<String, GitBranch> branches) {
Map<String, GitBranch> retval = new LinkedHashMap<>(branches.size());
return retval;
}
/**
* Provides information about what happened to a file between two different commits.
* If the file is copied or renamed between the two commits, you can get the path
* of the original file.
*/
public static final class GitFileInfo {
/**
* State of the file in the second commit in relevance to the first commit.
*/
public static enum Status {
ADDED,
MODIFIED,
RENAMED,
COPIED,
REMOVED,
UNKNOWN
}
private final String relativePath;
private final String originalPath;
private final Status status;
private final File file;
private final File originalFile;
GitFileInfo (File file, String relativePath, Status status, File originalFile, String originalPath) {
this.relativePath = relativePath;
this.status = status;
this.file = file;
this.originalFile = originalFile;
this.originalPath = originalPath;
}
/**
* @return relative path of the file to the root of the repository
*/
public String getRelativePath() {
return relativePath;
}
/**
* @return the relative path of the original file this file was copied or renamed from.
* For other statuses than <code>COPIED</code> or <code>RENAMED</code> it may be <code>null</code>
* or the same as the return value of <code>getPath</code> method
*/
public String getOriginalPath() {
return originalPath;
}
/**
* @return state of the file between the two commits
*/
public Status getStatus() {
return status;
}
/**
* @return the file this refers to
*/
public File getFile () {
return file;
}
/**
* @return the original file this file was copied or renamed from.
* For other statuses than <code>COPIED</code> or <code>RENAMED</code> it may be <code>null</code>
* or the same as the return value of <code>getFile</code> method
*/
public File getOriginalFile () {
return originalFile;
}
}
}