/******************************************************************************* * Copyright (C) 2013, Laurent Goubet <laurent.goubet@obeo.fr> * * 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 *******************************************************************************/ package org.eclipse.egit.core.test; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber; import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData; import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.team.core.RepositoryProvider; import org.eclipse.team.core.diff.IDiff; import org.eclipse.team.core.diff.IThreeWayDiff; import org.eclipse.team.core.history.IFileHistory; import org.eclipse.team.core.history.IFileHistoryProvider; import org.eclipse.team.core.history.IFileRevision; import org.eclipse.team.core.mapping.IResourceDiff; import org.junit.After; import org.junit.Before; import org.junit.Test; public class HistoryTest extends GitTestCase { private static final String MASTER = Constants.R_HEADS + Constants.MASTER; private static final String BRANCH = Constants.R_HEADS + "branch"; private TestRepository testRepository; private IFile iFile1; private IFile iFile2; private final List<RevCommit> commits = new ArrayList<RevCommit>(); private RevCommit masterCommit1; private RevCommit masterCommit2; private RevCommit masterCommit3; private RevCommit branchCommit1; private RevCommit branchCommit2; private IFileHistoryProvider historyProvider; @Override @Before public void setUp() throws Exception { super.setUp(); testRepository = new TestRepository(gitDir); testRepository.connect(project.getProject()); File file1 = testRepository.createFile(project.getProject(), "file1"); File file2 = testRepository.createFile(project.getProject(), "file2"); iFile1 = testRepository.getIFile(project.getProject(), file1); iFile2 = testRepository.getIFile(project.getProject(), file2); masterCommit1 = testRepository.addAndCommit(project.getProject(), file1, "master-commit-1"); masterCommit2 = testRepository.addAndCommit(project.getProject(), file2, "master-commit-2"); testRepository.createBranch(MASTER, BRANCH); testRepository.appendFileContent(file1, "master-commit-3"); testRepository.appendFileContent(file2, "master-commit-3"); testRepository.track(file1); testRepository.track(file2); testRepository.addToIndex(project.getProject(), file1); testRepository.addToIndex(project.getProject(), file2); masterCommit3 = testRepository.commit("master-commit-3"); testRepository.checkoutBranch(BRANCH); branchCommit1 = testRepository.appendContentAndCommit( project.getProject(), file1, "branch-commit-1", "branch-commit-1"); branchCommit2 = testRepository.appendContentAndCommit( project.getProject(), file2, "branch-commit-2", "branch-commit-2"); commits.add(masterCommit1); commits.add(masterCommit2); commits.add(masterCommit3); commits.add(branchCommit1); historyProvider = RepositoryProvider.getProvider(project.getProject()) .getFileHistoryProvider(); } @Override @After public void tearDown() throws Exception { testRepository.dispose(); super.tearDown(); } @Test public void queryFile1FullHistory() throws CoreException { final List<RevCommit> expectedHistory = Arrays.asList(masterCommit1, masterCommit3, branchCommit1); assertFullHistoryMatches(iFile1, expectedHistory); } @Test public void queryFile2FullHistory() throws CoreException { final List<RevCommit> expectedHistory = Arrays.asList(masterCommit2, masterCommit3, branchCommit2); assertFullHistoryMatches(iFile2, expectedHistory); } private void assertFullHistoryMatches(IFile target, List<RevCommit> expectedHistory) throws CoreException { // Whatever the position of HEAD, the history should be the same for (RevCommit commit : commits) { testRepository.checkoutBranch(commit.getName()); final IFileHistory history = historyProvider.getFileHistoryFor( target, IFileHistoryProvider.NONE, new NullProgressMonitor()); assertNotNull(history); final IFileRevision[] revisions = history.getFileRevisions(); assertEquals(expectedHistory.size(), revisions.length); final List<RevCommit> commitList = new ArrayList<RevCommit>( expectedHistory); assertMatchingRevisions(Arrays.asList(revisions), commitList); } } @Test public void querySingleRevisions() throws CoreException { for (RevCommit commit : commits) { for (IFile target : Arrays.asList(iFile1, iFile2)) { testRepository.checkoutBranch(commit.getName()); final IFileHistory history = historyProvider.getFileHistoryFor( target, IFileHistoryProvider.SINGLE_REVISION, new NullProgressMonitor()); assertNotNull(history); final IFileRevision[] revisions = history.getFileRevisions(); assertEquals(1, revisions.length); assertRevisionMatchCommit(revisions[0], commit); } } } @Test public void queryFile1Contributors() { final IFileHistory history = historyProvider.getFileHistoryFor(iFile1, IFileHistoryProvider.NONE, new NullProgressMonitor()); assertNotNull(history); final IFileRevision[] revisions = history.getFileRevisions(); IFileRevision branchFileRevision1 = null; IFileRevision masterFileRevision3 = null; IFileRevision masterFileRevision1 = null; for (IFileRevision revision : revisions) { final String revisionId = revision.getContentIdentifier(); if (branchCommit1.getName().equals(revisionId)) branchFileRevision1 = revision; else if (masterCommit3.getName().equals(revisionId)) masterFileRevision3 = revision; else if (masterCommit1.getName().equals(revisionId)) masterFileRevision1 = revision; } assertNotNull(branchFileRevision1); assertNotNull(masterFileRevision3); assertNotNull(masterFileRevision1); /* * The "direct" parent of branchCommit1 is masterCommit2. However, that * commit did not contain file1. We thus expect the returned contributor * to be masterCommit1. */ final IFileRevision[] branchCommit1Parents = history .getContributors(branchFileRevision1); assertEquals(1, branchCommit1Parents.length); assertRevisionMatchCommit(branchCommit1Parents[0], masterCommit1); // Likewise for masterCommit3 final IFileRevision[] masterCommit3Parents = history .getContributors(masterFileRevision3); assertEquals(1, masterCommit3Parents.length); assertRevisionMatchCommit(masterCommit3Parents[0], masterCommit1); // masterCommit1 is our initial commit final IFileRevision[] masterCommit1Parents = history .getContributors(masterFileRevision1); assertEquals(0, masterCommit1Parents.length); } @Test public void queryFile2Contributors() { final IFileHistory history = historyProvider.getFileHistoryFor(iFile2, IFileHistoryProvider.NONE, new NullProgressMonitor()); assertNotNull(history); final IFileRevision[] revisions = history.getFileRevisions(); IFileRevision masterFileRevision3 = null; IFileRevision masterFileRevision2 = null; IFileRevision branchFileRevision2 = null; for (IFileRevision revision : revisions) { final String revisionId = revision.getContentIdentifier(); if (masterCommit3.getName().equals(revisionId)) masterFileRevision3 = revision; else if (masterCommit2.getName().equals(revisionId)) masterFileRevision2 = revision; else if (branchCommit2.getName().equals(revisionId)) branchFileRevision2 = revision; } assertNotNull(masterFileRevision3); assertNotNull(masterFileRevision2); assertNotNull(branchFileRevision2); final IFileRevision[] masterCommit3Parents = history .getContributors(masterFileRevision3); assertEquals(1, masterCommit3Parents.length); assertRevisionMatchCommit(masterCommit3Parents[0], masterCommit2); /* * The direct parent of masterCommit2 is the initial commit, * masterCommit1. However, file2 was not included in that commit. We * thus expect no parent. */ final IFileRevision[] masterCommit2Parents = history .getContributors(masterFileRevision2); assertEquals(0, masterCommit2Parents.length); final IFileRevision[] branchCommit2Parents = history .getContributors(branchFileRevision2); assertEquals(1, branchCommit2Parents.length); assertRevisionMatchCommit(branchCommit2Parents[0], masterCommit2); } @Test public void queryFile1Targets() { final IFileHistory history = historyProvider.getFileHistoryFor(iFile1, IFileHistoryProvider.NONE, new NullProgressMonitor()); assertNotNull(history); final IFileRevision[] revisions = history.getFileRevisions(); IFileRevision branchFileRevision1 = null; IFileRevision masterFileRevision3 = null; IFileRevision masterFileRevision1 = null; for (IFileRevision revision : revisions) { final String revisionId = revision.getContentIdentifier(); if (branchCommit1.getName().equals(revisionId)) branchFileRevision1 = revision; else if (masterCommit3.getName().equals(revisionId)) masterFileRevision3 = revision; else if (masterCommit1.getName().equals(revisionId)) masterFileRevision1 = revision; } assertNotNull(branchFileRevision1); assertNotNull(masterFileRevision3); assertNotNull(masterFileRevision1); /* * The "direct" child of masterCommit1 is masterCommit2. However, that * commit did not contain file1. We thus expect the returned children to * be masterCommit3 and branchCommit1, since the ignored masterCommit2 * is a branching point. */ final IFileRevision[] masterCommit1Children = history .getTargets(masterFileRevision1); assertEquals(2, masterCommit1Children.length); final List<RevCommit> expected = new ArrayList<RevCommit>( Arrays.asList(masterCommit3, branchCommit1)); assertMatchingRevisions(Arrays.asList(masterCommit1Children), expected); // masterCommit3 and branchCommit1 are leafs final IFileRevision[] masterCommit3Children = history .getTargets(masterFileRevision3); assertEquals(0, masterCommit3Children.length); final IFileRevision[] branchCommit1Children = history .getTargets(branchFileRevision1); assertEquals(0, branchCommit1Children.length); } @Test public void queryFile2Targets() { final IFileHistory history = historyProvider.getFileHistoryFor(iFile2, IFileHistoryProvider.NONE, new NullProgressMonitor()); assertNotNull(history); final IFileRevision[] revisions = history.getFileRevisions(); IFileRevision masterFileRevision3 = null; IFileRevision masterFileRevision2 = null; IFileRevision branchFileRevision2 = null; for (IFileRevision revision : revisions) { final String revisionId = revision.getContentIdentifier(); if (masterCommit3.getName().equals(revisionId)) masterFileRevision3 = revision; else if (masterCommit2.getName().equals(revisionId)) masterFileRevision2 = revision; else if (branchCommit2.getName().equals(revisionId)) branchFileRevision2 = revision; } assertNotNull(masterFileRevision3); assertNotNull(masterFileRevision2); assertNotNull(branchFileRevision2); final IFileRevision[] masterCommit2Children = history .getTargets(masterFileRevision2); assertEquals(2, masterCommit2Children.length); assertTrue(Arrays.asList(masterCommit2Children).contains( masterFileRevision3)); assertTrue(Arrays.asList(masterCommit2Children).contains( branchFileRevision2)); final IFileRevision[] masterCommit3Children = history .getTargets(masterFileRevision3); assertEquals(0, masterCommit3Children.length); final IFileRevision[] branchCommit2Children = history .getTargets(branchFileRevision2); assertEquals(0, branchCommit2Children.length); } /* * This aims at exerting the behavior of the EGit history provider when used * through the Team APIs. This is the behavior extenders will see when * interfacing with EGit through the synchronize view. * * The exact comparison with which we've reached the synchronize perspective * should not be relevant. To keep this test as short as possible, we'll * only test a single comparison. */ @Test public void queryHistoryThroughTeam() throws IOException, CoreException { GitSynchronizeData gsd = new GitSynchronizeData( testRepository.getRepository(), MASTER, BRANCH, false); GitSynchronizeDataSet gsds = new GitSynchronizeDataSet(gsd); GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber( gsds); subscriber.init(new NullProgressMonitor()); IDiff diff = subscriber.getDiff(iFile2); assertTrue(diff instanceof IThreeWayDiff); IFileRevision sourceRevision = getSource(diff); IFileRevision destinationRevision = getDestination(diff); IFileRevision baseRevision = getBase(diff); assertRevisionMatchCommit(baseRevision, masterCommit2); assertRevisionMatchCommit(destinationRevision, branchCommit2); assertRevisionMatchCommit(sourceRevision, masterCommit3); final IFileHistory history = historyProvider.getFileHistoryFor(iFile2, IFileHistoryProvider.NONE, new NullProgressMonitor()); assertNotNull(history); // no parent of masterCommit2 in file2's history IFileRevision[] parents = history.getContributors(baseRevision); assertEquals(0, parents.length); /* * branchCommit1 did not contain file2, so the "child" of masterCommit2 * (branching point) in file2's history is branchCommit2. */ IFileRevision[] children = history.getTargets(baseRevision); List<RevCommit> expectedChildren = new ArrayList<RevCommit>( Arrays.asList(masterCommit3, branchCommit2)); assertEquals(expectedChildren.size(), children.length); assertMatchingRevisions(Arrays.asList(children), expectedChildren); } private static IFileRevision getSource(IDiff diff) { if (diff instanceof IResourceDiff) return ((IResourceDiff) diff).getBeforeState(); if (diff instanceof IThreeWayDiff) { final IThreeWayDiff twd = (IThreeWayDiff) diff; final IDiff localChange = twd.getLocalChange(); if (localChange instanceof IResourceDiff) return ((IResourceDiff) localChange).getAfterState(); } return null; } private static IFileRevision getDestination(IDiff diff) { if (diff instanceof IResourceDiff) return ((IResourceDiff) diff).getAfterState(); if (diff instanceof IThreeWayDiff) { final IThreeWayDiff twd = (IThreeWayDiff) diff; final IDiff remoteChange = twd.getRemoteChange(); if (remoteChange instanceof IResourceDiff) return ((IResourceDiff) remoteChange).getAfterState(); final IDiff localChange = twd.getLocalChange(); if (localChange instanceof IResourceDiff) return ((IResourceDiff) localChange).getBeforeState(); } return null; } private static IFileRevision getBase(IDiff diff) { if (diff instanceof IThreeWayDiff) { final IThreeWayDiff twd = (IThreeWayDiff) diff; final IDiff remoteChange = twd.getRemoteChange(); if (remoteChange instanceof IResourceDiff) return ((IResourceDiff) remoteChange).getBeforeState(); final IDiff localChange = twd.getLocalChange(); if (localChange instanceof IResourceDiff) return ((IResourceDiff) localChange).getBeforeState(); } return null; } private static void assertRevisionMatchCommit(IFileRevision revision, RevCommit commit) { assertEquals(commit.getAuthorIdent().getName(), revision.getAuthor()); assertEquals(commit.getFullMessage(), revision.getComment()); assertEquals(commit.getName(), revision.getContentIdentifier()); // Git is in seconds, Team in milliseconds assertEquals(commit.getCommitTime(), revision.getTimestamp() / 1000); } private static void assertMatchingRevisions(List<IFileRevision> revisions, List<RevCommit> commits) { assertEquals(commits.size(), revisions.size()); // Copy list : we'll empty it as we go for (IFileRevision revision : revisions) { boolean foundMatch = false; final Iterator<RevCommit> commitIterator = commits.iterator(); while (commitIterator.hasNext() && !foundMatch) { final RevCommit commit = commitIterator.next(); if (revision.getContentIdentifier().equals(commit.getName())) { assertRevisionMatchCommit(revision, commit); foundMatch = true; commitIterator.remove(); } } assertTrue(foundMatch); } assertTrue(commits.isEmpty()); } }