| // Copyright 2010 Victor Iacoban |
| // |
| // 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 org.zmlx.hg4idea.test; |
| |
| import com.intellij.execution.process.ProcessOutput; |
| import com.intellij.openapi.progress.EmptyProgressIndicator; |
| import com.intellij.openapi.vcs.VcsException; |
| import com.intellij.openapi.vcs.VcsTestUtil; |
| import com.intellij.openapi.vcs.update.UpdatedFiles; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import org.testng.annotations.BeforeMethod; |
| import org.testng.annotations.Test; |
| import org.zmlx.hg4idea.HgChange; |
| import org.zmlx.hg4idea.HgFile; |
| import org.zmlx.hg4idea.HgFileStatusEnum; |
| import org.zmlx.hg4idea.HgRevisionNumber; |
| import org.zmlx.hg4idea.command.*; |
| import org.zmlx.hg4idea.provider.update.HgRegularUpdater; |
| import org.zmlx.hg4idea.provider.update.HgUpdateConfigurationSettings; |
| import org.zmlx.hg4idea.util.HgUtil; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static org.testng.Assert.*; |
| |
| @SuppressWarnings({"ConstantConditions", "ThrowableResultOfMethodCallIgnored"}) |
| public class HgUpdateTest extends HgCollaborativeTest { |
| |
| private VirtualFile projectRepoVirtualFile; |
| private File projectRepo; |
| private File remoteRepo; |
| |
| @BeforeMethod |
| @Override |
| protected void setUp(Method testMethod) throws Exception { |
| super.setUp(testMethod); |
| projectRepoVirtualFile = myRepo.getDir(); |
| projectRepo = new File(myRepo.getDir().getPath()); |
| remoteRepo = new File(myParentRepo.getDir().getPath()); |
| } |
| |
| @Test |
| public void updateKeepsWorkingAfterPull() throws Exception { |
| |
| changeFile_A_AndCommitInRemoteRepository(); |
| |
| //do a simple pull without an update |
| HgPullCommand pull = new HgPullCommand(myProject, projectRepoVirtualFile); |
| pull.setSource(HgUtil.getRepositoryDefaultPath(myProject, projectRepoVirtualFile)); |
| pull.execute(); |
| |
| assertEquals( determineNumberOfIncomingChanges( projectRepo ), 0, |
| "The update operation should have pulled the incoming changes from the default repository." ); |
| |
| updateThroughPlugin(); |
| |
| HgRevisionNumber parentRevision = new HgWorkingCopyRevisionsCommand(myProject).firstParent(projectRepoVirtualFile); |
| assertEquals( parentRevision.getRevision(), "1", |
| "The working directory should have been updated to the latest version" ); |
| |
| } |
| |
| @Test |
| public void updateShouldMergeAndCommitChanges() throws Exception{ |
| changeFile_A_AndCommitInRemoteRepository(); |
| createAndCommitNewFileInLocalRepository(); |
| //we've got diverging heads in remote and local now |
| |
| PreUpdateInformation preUpdateInformation = new PreUpdateInformation().getPreUpdateInformation(); |
| HgRevisionNumber incomingHead = preUpdateInformation.getIncomingHead(); |
| HgRevisionNumber headBeforeUpdate = preUpdateInformation.getHeadBeforeUpdate(); |
| |
| List<VcsException> warnings = updateThroughPlugin(); |
| |
| assertEquals(warnings.size(), 0, "Plain update should not generate warnings"); |
| |
| assertCurrentHeadIsMerge(incomingHead, headBeforeUpdate); |
| } |
| |
| @Test |
| public void updateShouldNotMergeIfMultipleHeadsBeforeUpdate() throws Exception { |
| changeFile_A_AndCommitInRemoteRepository(); |
| createAndCommitNewFileInLocalRepository(); |
| |
| //create multiple heads locally |
| HgUpdateCommand updateCommand = new HgUpdateCommand(myProject, projectRepoVirtualFile); |
| HgRevisionNumber parent = new HgParentsCommand(myProject).execute(projectRepoVirtualFile).get(0); |
| HgParentsCommand parentsCommand = new HgParentsCommand(myProject); |
| parentsCommand.setRevision(parent); |
| List<HgRevisionNumber> parents = parentsCommand.execute(projectRepoVirtualFile); |
| updateCommand.setRevision(parents.get(0).getChangeset()); |
| updateCommand.execute(); |
| |
| createFileInCommand(projectRepoVirtualFile.findChild("com"),"c.txt", "updated content"); |
| runHg(projectRepo, "commit", "-m", "creating new local head"); |
| |
| List<HgRevisionNumber> branchHeads = new HgHeadsCommand(myProject, projectRepoVirtualFile).execute(); |
| assertEquals(branchHeads.size(), 2); |
| |
| HgRevisionNumber parentBeforeUpdate = new HgWorkingCopyRevisionsCommand(myProject).identify(projectRepoVirtualFile).getFirst(); |
| assertUpdateThroughPluginFails(); |
| HgRevisionNumber parentAfterUpdate = new HgWorkingCopyRevisionsCommand(myProject).identify(projectRepoVirtualFile).getFirst(); |
| List<HgRevisionNumber> branchHeadsAfterUpdate = new HgHeadsCommand(myProject, projectRepoVirtualFile).execute(); |
| |
| assertEquals(branchHeadsAfterUpdate.size(), 3); |
| assertEquals(parentBeforeUpdate, parentAfterUpdate); |
| } |
| |
| @Test |
| public void localChangesShouldBeAllowedWithFastForwardUpdate() throws Exception{ |
| createFileInCommand(projectRepoVirtualFile.findChild("com"), "b.txt", "other file"); |
| runHg(projectRepo, "commit", "-m", "adding second file"); |
| runHg(projectRepo, "push"); |
| |
| runHg(remoteRepo, "update"); |
| changeFile_A_AndCommitInRemoteRepository(); |
| |
| fillFile(projectRepo, new String[]{"com", "b.txt"}, "local change"); |
| createFileInCommand(projectRepoVirtualFile.findChild("com"), "c.txt", "other file"); |
| |
| |
| assertIsChanged(HgFileStatusEnum.MODIFIED, "com", "b.txt"); |
| assertIsChanged(HgFileStatusEnum.ADDED, "com", "c.txt"); |
| |
| PreUpdateInformation information = new PreUpdateInformation().getPreUpdateInformation(); |
| HgRevisionNumber incomingHead = information.getIncomingHead(); |
| |
| List<VcsException> nonFatalWarnings = updateThroughPlugin(); |
| |
| assertTrue(nonFatalWarnings.isEmpty()); |
| HgRevisionNumber parentAfterUpdate = new HgParentsCommand(myProject).execute(projectRepoVirtualFile).get(0); |
| assertEquals(incomingHead, parentAfterUpdate); |
| |
| assertIsChanged(HgFileStatusEnum.MODIFIED, "com", "b.txt"); |
| assertIsChanged(HgFileStatusEnum.ADDED, "com", "c.txt"); |
| } |
| |
| @Test |
| public void updateShouldNotMergeTwoHeadsComingFromRemote() throws Exception { |
| String originalParent = runHg(remoteRepo, "parents", "--template", "{rev}\n").getStdout().trim(); |
| changeFile_A_AndCommitInRemoteRepository(); |
| |
| runHg(remoteRepo, "update", "--clean", originalParent); |
| |
| File file = fillFile(remoteRepo, new String[]{"com", "b.txt"}, "second file"); |
| runHg(remoteRepo, "add", file.getPath()); |
| runHg(remoteRepo, "commit", "-m", "adding second file"); |
| |
| assertUpdateThroughPluginFails(); |
| |
| List<HgRevisionNumber> branchHeads = new HgHeadsCommand(myProject, projectRepoVirtualFile).execute(); |
| assertEquals(branchHeads.size(), 2); |
| |
| |
| } |
| |
| private void assertIsChanged(HgFileStatusEnum status, String... filepath) { |
| Set<HgChange> localChanges = new HgStatusCommand.Builder(true).build(myProject).execute(projectRepoVirtualFile); |
| assertTrue(localChanges.contains(new HgChange(getHgFile(filepath), status))); |
| } |
| |
| @Override |
| protected HgFile getHgFile(String... filepath) { |
| File fileToInclude = projectRepo; |
| for (int i = 0; i < filepath.length; i++) { |
| fileToInclude = new File(fileToInclude, filepath[i]); |
| } |
| return new HgFile(projectRepoVirtualFile, fileToInclude); |
| } |
| |
| private void assertCurrentHeadIsMerge(HgRevisionNumber incomingHead, HgRevisionNumber headBeforeUpdate) { |
| List<HgRevisionNumber> newHeads = new HgHeadsCommand(myProject, projectRepoVirtualFile).execute(); |
| assertEquals(newHeads.size(), 1, "After updating, there should be only one head because the remote heads should have been merged"); |
| HgRevisionNumber newHead = newHeads.get(0); |
| HgParentsCommand parents = new HgParentsCommand(myProject); |
| parents.setRevision(newHead); |
| List<HgRevisionNumber> parentRevisions = parents.execute(projectRepoVirtualFile); |
| assertEquals(parentRevisions.size(), 2); |
| assertTrue(parentRevisions.contains(incomingHead)); |
| assertTrue(parentRevisions.contains(headBeforeUpdate)); |
| } |
| |
| @Test |
| public void updateShouldMergeButNotCommitWithConflicts() throws Exception{ |
| changeFile_A_AndCommitInRemoteRepository(); |
| |
| VirtualFile commonFile = projectRepoVirtualFile.findChild("com").findChild("a.txt"); |
| assertNotNull(commonFile); |
| |
| VcsTestUtil.editFileInCommand(myProject, commonFile, "conflicting content"); |
| runHg(projectRepo, "commit", "-m", "adding conflicting history to local repository"); |
| |
| PreUpdateInformation preUpdateInformation = new PreUpdateInformation().getPreUpdateInformation(); |
| HgRevisionNumber incomingHead = preUpdateInformation.getIncomingHead(); |
| HgRevisionNumber headBeforeUpdate = preUpdateInformation.getHeadBeforeUpdate(); |
| |
| List<VcsException> warnings = updateThroughPlugin(); |
| assertFalse(warnings.isEmpty()); |
| assertTrue(warnings.get(warnings.size()-1).getMessage().contains("conflicts")); |
| assertTrue(warnings.get(warnings.size()-1).getMessage().contains("commit")); |
| |
| List<HgRevisionNumber> parents = new HgWorkingCopyRevisionsCommand(myProject).parents(projectRepoVirtualFile); |
| assertEquals(parents.size(), 2); |
| assertTrue(parents.contains(incomingHead)); |
| assertTrue(parents.contains(headBeforeUpdate)); |
| } |
| |
| @Test |
| public void updateShouldNotMergeWithNonCommittedChanges() throws Exception { |
| changeFile_A_AndCommitInRemoteRepository(); |
| |
| //generate some extra local history |
| createAndCommitNewFileInLocalRepository(); |
| |
| HgRevisionNumber parentBeforeUpdate = new HgWorkingCopyRevisionsCommand(myProject).parents(projectRepoVirtualFile).get(0); |
| |
| VcsTestUtil.editFileInCommand(myProject, projectRepoVirtualFile.findFileByRelativePath("com/a.txt"), "modified file contents"); |
| |
| assertUpdateThroughPluginFails(); |
| |
| assertEquals( new HgHeadsCommand( myProject, projectRepoVirtualFile ).execute().size(), 2, |
| "Remote head should have been pulled in" ); |
| |
| assertEquals( new HgWorkingCopyRevisionsCommand( myProject ).parents( projectRepoVirtualFile ).size(), 1, |
| "No merge should have been attempted" ); |
| |
| assertEquals( new HgWorkingCopyRevisionsCommand( myProject ).parents( projectRepoVirtualFile ).get(0), parentBeforeUpdate, |
| "No merge should have been attempted" ); |
| } |
| |
| private void assertUpdateThroughPluginFails() { |
| try { |
| updateThroughPlugin(); |
| fail("The update should have failed because a merge cannot be initiated with outstanding changes"); |
| } catch (VcsException e) { |
| //expected |
| } |
| } |
| |
| private void createAndCommitNewFileInLocalRepository() throws IOException { |
| createFileInCommand(projectRepoVirtualFile.findChild("com"), "b.txt", "other file"); |
| runHg(projectRepo, "commit", "-m", "adding non-conflicting history to local repository"); |
| } |
| |
| private List<VcsException> updateThroughPlugin() throws VcsException { |
| HgRegularUpdater updater = new HgRegularUpdater(myProject, projectRepoVirtualFile, new HgUpdateConfigurationSettings()); |
| UpdatedFiles updatedFiles = UpdatedFiles.create(); |
| EmptyProgressIndicator indicator = new EmptyProgressIndicator(); |
| ArrayList<VcsException> nonFatalWarnings = new ArrayList<VcsException>(); |
| updater.update(updatedFiles, indicator, nonFatalWarnings); |
| return nonFatalWarnings; |
| } |
| |
| private void changeFile_A_AndCommitInRemoteRepository() throws IOException { |
| fillFile(remoteRepo, new String[]{"com", "a.txt"}, "update file contents"); |
| runHg(remoteRepo, "commit", "-m", "Adding history to remote repository"); |
| |
| assertEquals( determineNumberOfIncomingChanges( projectRepo ), 1, |
| "The remote repository should have gotten new history" ); |
| } |
| |
| private int determineNumberOfIncomingChanges(File repo) throws IOException { |
| ProcessOutput result = runHg(repo, "-q", "incoming", "--template", "{rev} "); |
| String output = result.getStdout(); |
| return output.length() == 0 ? 0 : output.split(" ").length; |
| } |
| |
| private class PreUpdateInformation { |
| private HgRevisionNumber incomingHead; |
| private HgRevisionNumber headBeforeUpdate; |
| |
| public HgRevisionNumber getIncomingHead() { |
| return incomingHead; |
| } |
| |
| public HgRevisionNumber getHeadBeforeUpdate() { |
| return headBeforeUpdate; |
| } |
| |
| public PreUpdateInformation getPreUpdateInformation() { |
| List<HgRevisionNumber> currentHeads = new HgHeadsCommand(myProject, projectRepoVirtualFile).execute(); |
| List<HgRevisionNumber> incomingChangesets = new HgIncomingCommand(myProject).execute(projectRepoVirtualFile); |
| |
| assertEquals(currentHeads.size(), 1); |
| assertEquals(incomingChangesets.size(), 1); |
| |
| incomingHead = incomingChangesets.get(0); |
| headBeforeUpdate = currentHeads.get(0); |
| return this; |
| } |
| } |
| } |
| |