| /******************************************************************************* |
| * 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 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| 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<>(); |
| |
| 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<>( |
| 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<>( |
| 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<>( |
| 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()); |
| } |
| } |