blob: e8dfd458f45eec824de4ff914fbe08fcef608de0 [file] [log] [blame]
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed 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 git4idea.history;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import git4idea.GitUtil;
import git4idea.test.GitPlatformTest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import static git4idea.history.GitLogParser.*;
import static git4idea.history.GitLogParser.GitLogOption.*;
public class GitLogParserTest extends GitPlatformTest {
public static final GitLogOption[] GIT_LOG_OPTIONS =
new GitLogOption[]{HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_TIME, AUTHOR_EMAIL, COMMITTER_NAME,
COMMITTER_EMAIL, SUBJECT, BODY, PARENTS, PARENTS, RAW_BODY
};
private VirtualFile myRoot;
private GitLogParser myParser;
private GitTestLogRecord myRecord;
private static final GitTestLogRecord RECORD1 = new GitTestLogRecord(ContainerUtil.<GitTestLogRecordInfo, Object>immutableMapBuilder()
.put(GitTestLogRecordInfo.HASH, "2c815939f45fbcfda9583f84b14fe9d393ada790")
.put(GitTestLogRecordInfo.AUTHOR_TIME, new Date(1317027817L * 1000))
.put(GitTestLogRecordInfo.AUTHOR_NAME, "John Doe")
.put(GitTestLogRecordInfo.AUTHOR_EMAIL, "John.Doe@example.com")
.put(GitTestLogRecordInfo.COMMIT_TIME, new Date(1315471452L * 1000))
.put(GitTestLogRecordInfo.COMMIT_NAME, "Bob Smith")
.put(GitTestLogRecordInfo.COMMIT_EMAIL, "Bob@site.com")
.put(GitTestLogRecordInfo.SUBJECT, "Commit message")
.put(GitTestLogRecordInfo.BODY, "Description goes here\n" +
"\n" + // empty line
"Then comes a long long description.\n" +
"Probably multilined.")
.put(GitTestLogRecordInfo.PARENTS, new String[] {
"c916c63b89d8fa81ebf23cc5cbcdb75e115623c7",
"7c1298fd1f93df414ce0d87128532f819de2cbd4"})
.put(GitTestLogRecordInfo.CHANGES, new GitTestChange[]{
GitTestChange.moved("file2", "file3"),
GitTestChange.added("readme.txt"),
GitTestChange.modified("src/CClass.java"),
GitTestChange.deleted("src/ChildAClass.java")})
.build());
private static final GitTestLogRecord RECORD2 = new GitTestLogRecord(ContainerUtil.<GitTestLogRecordInfo, Object>immutableMapBuilder()
.put(GitTestLogRecordInfo.HASH, "c916c63b89d8fa81ebf23cc5cbcdb75e115623c7")
.put(GitTestLogRecordInfo.AUTHOR_TIME, new Date(1317027817L * 1000))
.put(GitTestLogRecordInfo.AUTHOR_NAME, "John Doe")
.put(GitTestLogRecordInfo.AUTHOR_EMAIL, "John.Doe@example.com")
.put(GitTestLogRecordInfo.COMMIT_TIME, new Date(1315471452L * 1000))
.put(GitTestLogRecordInfo.COMMIT_NAME, "John Doe")
.put(GitTestLogRecordInfo.COMMIT_EMAIL, "John.Doe@example.com")
.put(GitTestLogRecordInfo.SUBJECT, "Commit message")
.put(GitTestLogRecordInfo.BODY, "Small description")
.put(GitTestLogRecordInfo.PARENTS, new String[] { "7c1298fd1f93df414ce0d87128532f819de2cbd4" })
.put(GitTestLogRecordInfo.CHANGES, new GitTestChange[] { GitTestChange.modified("src/CClass.java") })
.build());
private static final GitTestLogRecord RECORD3 = new GitTestLogRecord(ContainerUtil.<GitTestLogRecordInfo, Object>immutableMapBuilder()
.put(GitTestLogRecordInfo.HASH, "c916c63b89d8fa81ebf23cc5cbcdb75e115623c7")
.put(GitTestLogRecordInfo.AUTHOR_TIME, new Date(1317027817L * 1000))
.put(GitTestLogRecordInfo.AUTHOR_NAME, "John Doe")
.put(GitTestLogRecordInfo.AUTHOR_EMAIL, "John.Doe@example.com")
.put(GitTestLogRecordInfo.COMMIT_TIME, new Date(1315471452L * 1000))
.put(GitTestLogRecordInfo.COMMIT_NAME, "John Doe")
.put(GitTestLogRecordInfo.COMMIT_EMAIL, "John.Doe@example.com")
.put(GitTestLogRecordInfo.SUBJECT, "Commit message")
.put(GitTestLogRecordInfo.BODY, "Small description")
.put(GitTestLogRecordInfo.PARENTS, new String[] { "7c1298fd1f93df414ce0d87128532f819de2cbd4" })
.put(GitTestLogRecordInfo.CHANGES, new GitTestChange[] { GitTestChange.modified("src/CClass.java") })
.build());
public static final List<GitTestLogRecord> ALL_RECORDS = Arrays.asList(RECORD1, RECORD2, RECORD3);
protected void setUp() throws Exception {
super.setUp();
myRoot = myProjectRoot;
myRecord = RECORD1; // for single record tests
}
public void testparseAllWithoutNameStatus() throws VcsException {
doTestAllRecords(GitTestLogRecord.NameStatusOption.NONE);
}
public void testparseAllWithName() throws VcsException {
doTestAllRecords(GitTestLogRecord.NameStatusOption.NAME);
}
public void testparseAllWithNameStatus() throws VcsException {
doTestAllRecords(GitTestLogRecord.NameStatusOption.STATUS);
}
private void doTestAllRecords(GitTestLogRecord.NameStatusOption nameStatusOption) throws VcsException {
NameStatus option;
switch (nameStatusOption) {
case NONE: option = NameStatus.NONE; break;
case NAME: option = NameStatus.NAME; break;
case STATUS: option = NameStatus.STATUS; break;
default: throw new AssertionError();
}
myParser = new GitLogParser(myProject, option, GIT_LOG_OPTIONS);
String output = prepareOutputForAllRecords(nameStatusOption);
List<GitLogRecord> actualRecords = myParser.parse(output);
List<GitTestLogRecord> expectedRecords = ALL_RECORDS;
assertAllRecords(actualRecords, expectedRecords, nameStatusOption);
}
public void testparseOneRecordWithoutNameStatus() throws VcsException {
myParser = new GitLogParser(myProject, GIT_LOG_OPTIONS);
doTestOneRecord(GitTestLogRecord.NameStatusOption.NONE);
}
public void testparseOneRecordWithName() throws VcsException {
myParser = new GitLogParser(myProject, NameStatus.NAME, GIT_LOG_OPTIONS);
doTestOneRecord(GitTestLogRecord.NameStatusOption.NAME);
}
public void testparseOneRecordWithNameStatus() throws VcsException {
myParser = new GitLogParser(myProject, NameStatus.STATUS, GIT_LOG_OPTIONS);
doTestOneRecord(GitTestLogRecord.NameStatusOption.STATUS);
}
private void doTestOneRecord(GitTestLogRecord.NameStatusOption option) throws VcsException {
String s = myRecord.prepareOutputLine(option);
GitLogRecord record = myParser.parseOneRecord(s);
assertRecord(record, myRecord, option);
}
private void assertAllRecords(List<GitLogRecord> actualRecords,
List<GitTestLogRecord> expectedRecords,
GitTestLogRecord.NameStatusOption nameStatusOption) throws VcsException {
assertEquals(actualRecords.size(), expectedRecords.size());
for (int i = 0; i < actualRecords.size(); i++) {
assertRecord(actualRecords.get(i), expectedRecords.get(i), nameStatusOption);
}
}
private static String prepareOutputForAllRecords(GitTestLogRecord.NameStatusOption nameStatusOption) {
StringBuilder sb = new StringBuilder();
for (GitTestLogRecord record : ALL_RECORDS) {
sb.append(record.prepareOutputLine(nameStatusOption)).append("\n");
}
return sb.toString();
}
private void assertRecord(GitLogRecord actual, GitTestLogRecord expected, GitTestLogRecord.NameStatusOption option) throws VcsException {
assertEquals(expected.getHash(), actual.getHash());
assertEquals(expected.getCommitterName(), actual.getCommitterName());
assertEquals(expected.getCommitterEmail(), actual.getCommitterEmail());
assertEquals(expected.getCommitTime(), actual.getDate());
assertEquals(expected.getAuthorName(), actual.getAuthorName());
assertEquals(expected.getAuthorEmail(), actual.getAuthorEmail());
assertEquals(expected.getAuthorTime().getTime(), actual.getAuthorTimeStamp());
String expectedAuthorAndCommitter = GitUtil.adjustAuthorName(
String.format("%s <%s>", expected.getAuthorName(), expected.getAuthorEmail()),
String.format("%s <%s>", expected.getCommitterName(), expected.getCommitterEmail()));
assertEquals(expectedAuthorAndCommitter, getAuthorAndCommitter(actual));
assertEquals(expected.getSubject(), actual.getSubject());
assertEquals(expected.getBody(), actual.getBody());
assertEquals(expected.rawBody(), actual.getRawBody());
assertSameElements(actual.getParentsHashes(), expected.getParents());
if (option == GitTestLogRecord.NameStatusOption.NAME) {
assertPaths(actual.getFilePaths(myRoot), expected.paths());
} else if (option == GitTestLogRecord.NameStatusOption.STATUS) {
assertPaths(actual.getFilePaths(myRoot), expected.paths());
assertChanges(actual.parseChanges(myProject, myRoot), expected.changes());
}
}
@NotNull
String getAuthorAndCommitter(@NotNull GitLogRecord actual) {
String author = String.format("%s <%s>", actual.getAuthorName(), actual.getAuthorEmail());
String committer = String.format("%s <%s>", actual.getCommitterName(), actual.getCommitterEmail());
return GitUtil.adjustAuthorName(author, committer);
}
private void assertPaths(List<FilePath> actualPaths, List<String> expectedPaths) {
List<String> actual = ContainerUtil.map(actualPaths, new Function<FilePath, String>() {
@Override
public String fun(FilePath path) {
return FileUtil.getRelativePath(new File(myProjectPath), path.getIOFile());
}
});
List<String> expected = ContainerUtil.map(expectedPaths, new Function<String, String>() {
@Override
public String fun(String s) {
return FileUtil.toSystemDependentName(s);
}
});
assertOrderedEquals(actual, expected);
}
private void assertChanges(List<Change> actual, List<GitTestChange> expected) {
assertEquals(expected.size(), actual.size());
for (int i = 0; i < actual.size(); i++) {
Change actualChange = actual.get(i);
GitTestChange expectedChange = expected.get(i);
assertChange(actualChange, expectedChange);
}
}
private void assertChange(Change actualChange, GitTestChange expectedChange) {
assertEquals(actualChange.getType(), expectedChange.myType);
switch (actualChange.getType()) {
case MODIFICATION:
case MOVED:
assertEquals(getBeforePath(actualChange), FileUtil.toSystemDependentName(expectedChange.myBeforePath));
assertEquals(getAfterPath(actualChange), FileUtil.toSystemDependentName(expectedChange.myAfterPath));
return;
case NEW:
assertEquals(getAfterPath(actualChange), FileUtil.toSystemDependentName(expectedChange.myAfterPath));
return;
case DELETED:
assertEquals(getBeforePath(actualChange), FileUtil.toSystemDependentName(expectedChange.myBeforePath));
return;
default:
throw new AssertionError();
}
}
private String getBeforePath(Change actualChange) {
return FileUtil.getRelativePath(new File(myProjectPath), actualChange.getBeforeRevision().getFile().getIOFile());
}
private String getAfterPath(Change actualChange) {
return FileUtil.getRelativePath(new File(myProjectPath), actualChange.getAfterRevision().getFile().getIOFile());
}
private enum GitTestLogRecordInfo {
HASH,
COMMIT_TIME,
AUTHOR_TIME,
AUTHOR_NAME,
AUTHOR_EMAIL,
COMMIT_NAME,
COMMIT_EMAIL,
SUBJECT,
BODY,
PARENTS,
CHANGES
}
private static class GitTestLogRecord {
private final Map<GitTestLogRecordInfo, Object> myData;
GitTestLogRecord(Map<GitTestLogRecordInfo, Object> data) {
myData = data;
}
public String getHash() {
return (String)myData.get(GitTestLogRecordInfo.HASH);
}
public Date getCommitTime() {
return (Date)myData.get(GitTestLogRecordInfo.COMMIT_TIME);
}
public Date getAuthorTime() {
return (Date)myData.get(GitTestLogRecordInfo.AUTHOR_TIME);
}
public String getAuthorName() {
return (String)myData.get(GitTestLogRecordInfo.AUTHOR_NAME);
}
public String getAuthorEmail() {
return (String)myData.get(GitTestLogRecordInfo.AUTHOR_EMAIL);
}
public String getCommitterName() {
return (String)myData.get(GitTestLogRecordInfo.COMMIT_NAME);
}
public String getCommitterEmail() {
return (String)myData.get(GitTestLogRecordInfo.COMMIT_EMAIL);
}
public String getSubject() {
return (String)myData.get(GitTestLogRecordInfo.SUBJECT);
}
public String getBody() {
return (String)myData.get(GitTestLogRecordInfo.BODY);
}
public String[] getParents() {
return (String[])myData.get(GitTestLogRecordInfo.PARENTS);
}
public GitTestChange[] getChanges() {
return (GitTestChange[])myData.get(GitTestLogRecordInfo.CHANGES);
}
String[] shortParents() {
String[] parents = getParents();
String[] shortParents = new String[parents.length];
for (int i = 0 ; i < parents.length; i++) {
shortParents[i] = parents[i].substring(0, 7);
}
return shortParents;
}
private String shortParentsAsString() {
return StringUtil.join(shortParents(), " ");
}
private String parentsAsString() {
return StringUtil.join(getParents(), " ");
}
String rawBody() {
return getSubject() + "\n\n" + getBody();
}
List<GitTestChange> changes() {
return Arrays.asList(getChanges());
}
private String changesAsString() {
StringBuilder sb = new StringBuilder();
for (GitTestChange change : getChanges()) {
sb.append(change.toOutputString());
}
return sb.toString();
}
public List<String> paths() {
List<String> paths = new ArrayList<String>();
for (GitTestChange change : getChanges()) {
switch (change.myType) {
case MODIFICATION:
case NEW:
paths.add(change.myAfterPath);
break;
case DELETED:
paths.add(change.myBeforePath);
break;
case MOVED:
paths.add(change.myBeforePath);
paths.add(change.myAfterPath);
break;
default:
throw new AssertionError();
}
}
return paths;
}
private String pathsAsString() {
StringBuilder sb = new StringBuilder();
for (String path : paths()) {
sb.append(path).append("\n");
}
return sb.toString();
}
enum NameStatusOption {
NONE, NAME, STATUS
}
String prepareOutputLine(NameStatusOption nameStatusOption) {
StringBuilder sb = new StringBuilder(RECORD_START);
for (GitLogOption option : GIT_LOG_OPTIONS) {
sb.append(optionToValue(option)).append(ITEMS_SEPARATOR);
}
sb.append(RECORD_END);
if (nameStatusOption == NameStatusOption.NAME) {
sb.append("\n\n").append(pathsAsString());
}
else if (nameStatusOption == NameStatusOption.STATUS) {
sb.append("\n\n").append(changesAsString());
}
return sb.toString();
}
private String optionToValue(GitLogOption option) {
switch (option) {
case HASH:
return getHash();
case SUBJECT:
return getSubject();
case BODY:
return getBody();
case RAW_BODY:
return rawBody();
case COMMIT_TIME:
return String.valueOf(getCommitTime().getTime() / 1000);
case AUTHOR_NAME:
return getAuthorName();
case AUTHOR_TIME:
return String.valueOf(getAuthorTime().getTime() / 1000);
case AUTHOR_EMAIL:
return getAuthorEmail();
case COMMITTER_NAME:
return getCommitterName();
case COMMITTER_EMAIL:
return getCommitterEmail();
case PARENTS:
return parentsAsString();
case REF_NAMES:
break;
case SHORT_REF_LOG_SELECTOR:
break;
}
throw new AssertionError();
}
}
private static class GitTestChange {
final Change.Type myType;
final String myBeforePath;
final String myAfterPath;
GitTestChange(Change.Type type, String beforePath, String afterPath) {
myAfterPath = afterPath;
myBeforePath = beforePath;
myType = type;
}
static GitTestChange added(String path) {
return new GitTestChange(Change.Type.NEW, null, path);
}
static GitTestChange deleted(String path) {
return new GitTestChange(Change.Type.DELETED, path, null);
}
static GitTestChange modified(String path) {
return new GitTestChange(Change.Type.MODIFICATION, path, path);
}
static GitTestChange moved(String before, String after) {
return new GitTestChange(Change.Type.MOVED, before, after);
}
String toOutputString() {
switch (myType) {
case MOVED: return outputString("R100", myBeforePath, myAfterPath);
case MODIFICATION: return outputString("M", myBeforePath, null);
case DELETED: return outputString("D", myBeforePath, null);
case NEW: return outputString("A", myAfterPath, null);
default:
throw new AssertionError();
}
}
private static String outputString(@NotNull String type, @Nullable String beforePath, @Nullable String afterPath) {
StringBuilder sb = new StringBuilder();
sb.append(type).append("\t");
if (beforePath != null) {
sb.append(beforePath).append("\t");
}
if (afterPath != null) {
sb.append(afterPath).append("\t");
}
sb.append("\n");
return sb.toString();
}
}
}