| /* |
| * Copyright 2000-2012 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 org.jetbrains.idea.svn; |
| |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; |
| import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.idea.svn.api.Depth; |
| import org.jetbrains.idea.svn.dialogs.MergeContext; |
| import org.jetbrains.idea.svn.dialogs.WCInfo; |
| import org.jetbrains.idea.svn.history.SvnChangeList; |
| import org.jetbrains.idea.svn.history.SvnRepositoryLocation; |
| import org.jetbrains.idea.svn.info.Info; |
| import org.jetbrains.idea.svn.mergeinfo.BranchInfo; |
| import org.jetbrains.idea.svn.mergeinfo.OneShotMergeInfoHelper; |
| import org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache; |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.tmatesoft.svn.core.SVNException; |
| import org.tmatesoft.svn.core.SVNURL; |
| import org.tmatesoft.svn.core.internal.util.SVNPathUtil; |
| import org.tmatesoft.svn.core.wc.SVNPropertyData; |
| import org.tmatesoft.svn.core.wc.SVNRevision; |
| import org.tmatesoft.svn.core.wc.SVNWCClient; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.List; |
| |
| // TODO: Many tests in this class are written with direct SVNKit usage - could not utilize it for svn 1.8 |
| public class SvnMergeInfoTest extends Svn17TestCase { |
| |
| private static final String CONTENT1 = "123\n456\n123"; |
| private static final String CONTENT2 = "123\n456\n123\n4"; |
| |
| private File myBranchVcsRoot; |
| private ProjectLevelVcsManagerImpl myProjectLevelVcsManager; |
| private WCInfo myWCInfo; |
| private OneShotMergeInfoHelper myOneShotMergeInfoHelper; |
| |
| private SvnVcs myVcs; |
| private BranchInfo myMergeChecker; |
| |
| private File trunk; |
| private File folder; |
| private File folder1; |
| private File f1; |
| private File f2; |
| |
| private String myTrunkUrl; |
| private String myBranchUrl; |
| private MergeContext myMergeContext; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| myTrunkUrl = myRepoUrl + "/trunk"; |
| myBranchUrl = myRepoUrl + "/branch"; |
| |
| myBranchVcsRoot = new File(myTempDirFixture.getTempDirPath(), "branch"); |
| myBranchVcsRoot.mkdir(); |
| |
| myProjectLevelVcsManager = (ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject); |
| myProjectLevelVcsManager.setDirectoryMapping(myBranchVcsRoot.getAbsolutePath(), SvnVcs.VCS_NAME); |
| |
| VirtualFile vcsRoot = LocalFileSystem.getInstance().findFileByIoFile(myBranchVcsRoot); |
| Node node = new Node(vcsRoot, SVNURL.parseURIEncoded(myBranchUrl), SVNURL.parseURIEncoded(myRepoUrl)); |
| RootUrlInfo root = new RootUrlInfo(node, WorkingCopyFormat.ONE_DOT_SIX, vcsRoot, null); |
| myWCInfo = new WCInfo(root, true, Depth.INFINITY); |
| myMergeContext = new MergeContext(SvnVcs.getInstance(myProject), myTrunkUrl, myWCInfo, SVNPathUtil.tail(myTrunkUrl), vcsRoot); |
| myOneShotMergeInfoHelper = new OneShotMergeInfoHelper(myMergeContext); |
| |
| myVcs = SvnVcs.getInstance(myProject); |
| myVcs.getSvnConfiguration().setCheckNestedForQuickMerge(true); |
| |
| enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); |
| enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); |
| |
| final String repoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); |
| myMergeChecker = new BranchInfo(myVcs, repoUrl, repoUrl + "/branch", repoUrl + "/trunk", repoUrl + "/trunk"); |
| } |
| |
| @Test |
| public void testSimpleNotMerged() throws Exception { |
| createOneFolderStructure(); |
| |
| // rev 3 |
| editAndCommit(trunk, f1); |
| |
| assertMergeResult(getTrunkChangeLists(), SvnMergeInfoCache.MergeCheckResult.NOT_MERGED); |
| } |
| |
| @Test |
| public void testSimpleMerged() throws Exception { |
| createOneFolderStructure(); |
| |
| // rev 3 |
| editAndCommit(trunk, f1); |
| |
| // rev 4: record as merged into branch |
| recordMerge(myBranchVcsRoot, myTrunkUrl, "-c", "3"); |
| commitFile(myBranchVcsRoot); |
| updateFile(myBranchVcsRoot); |
| |
| assertMergeInfo(myBranchVcsRoot, "/trunk:3"); |
| assertMergeResult(getTrunkChangeLists(), SvnMergeInfoCache.MergeCheckResult.MERGED); |
| } |
| |
| @Test |
| public void testEmptyMergeinfoBlocks() throws Exception { |
| createOneFolderStructure(); |
| |
| // rev 3 |
| editAndCommit(trunk, f1); |
| |
| // rev 4: record as merged into branch |
| merge(myBranchVcsRoot, myTrunkUrl, "-c", "3"); |
| commitFile(myBranchVcsRoot); |
| updateFile(myBranchVcsRoot); |
| // rev5: put blocking empty mergeinfo |
| //runInAndVerifyIgnoreOutput("merge", "-c", "-3", myRepoUrl + "/trunk/folder", new File(myBranchVcsRoot, "folder").getAbsolutePath(), "--record-only")); |
| merge(new File(myBranchVcsRoot, "folder"), myTrunkUrl + "/folder", "-r", "3:2"); |
| commitFile(myBranchVcsRoot); |
| updateFile(myBranchVcsRoot); |
| |
| assertMergeInfo(myBranchVcsRoot, "/trunk:3"); |
| assertMergeInfo(new File(myBranchVcsRoot, "folder"), ""); |
| |
| assertMergeResult(getTrunkChangeLists(), SvnMergeInfoCache.MergeCheckResult.NOT_MERGED); |
| } |
| |
| @Test |
| public void testNonInheritableMergeinfo() throws Exception { |
| createOneFolderStructure(); |
| |
| // rev 3 |
| editAndCommit(trunk, f1); |
| |
| // rev 4: record non inheritable merge |
| setMergeInfo(myBranchVcsRoot, "/trunk:3*"); |
| commitFile(myBranchVcsRoot); |
| updateFile(myBranchVcsRoot); |
| |
| assertMergeInfo(myBranchVcsRoot, "/trunk:3*"); |
| |
| assertMergeResult(getTrunkChangeLists(), SvnMergeInfoCache.MergeCheckResult.NOT_MERGED); |
| } |
| |
| @Test |
| public void testOnlyImmediateInheritableMergeinfo() throws Exception { |
| createOneFolderStructure(); |
| |
| // rev 3 |
| editAndCommit(trunk, f1, CONTENT1); |
| // rev4 |
| editAndCommit(trunk, f1, CONTENT2); |
| |
| updateFile(myBranchVcsRoot); |
| |
| // rev 4: record non inheritable merge |
| setMergeInfo(myBranchVcsRoot, "/trunk:3,4"); |
| setMergeInfo(new File(myBranchVcsRoot, "folder"), "/trunk:3"); |
| commitFile(myBranchVcsRoot); |
| updateFile(myBranchVcsRoot); |
| |
| assertMergeInfo(myBranchVcsRoot, "/trunk:3-4", "/trunk:3,4"); |
| assertMergeInfo(new File(myBranchVcsRoot, "folder"), "/trunk:3"); |
| |
| final List<SvnChangeList> changeListList = getTrunkChangeLists(); |
| |
| assertRevisions(changeListList, 4, 3); |
| assertMergeResult(changeListList, SvnMergeInfoCache.MergeCheckResult.NOT_MERGED, SvnMergeInfoCache.MergeCheckResult.MERGED); |
| } |
| |
| @Test |
| public void testTwoPaths() throws Exception { |
| createTwoFolderStructure(myBranchVcsRoot); |
| |
| // rev 3 |
| editFile(f1); |
| editFile(f2); |
| commitFile(trunk); |
| |
| updateFile(myBranchVcsRoot); |
| |
| // rev 4: record non inheritable merge |
| setMergeInfo(myBranchVcsRoot, "/trunk:3"); |
| // this makes not merged for f2 path |
| setMergeInfo(new File(myBranchVcsRoot, "folder/folder1"), "/trunk:3*"); |
| commitFile(myBranchVcsRoot); |
| updateFile(myBranchVcsRoot); |
| |
| assertMergeInfo(myBranchVcsRoot, "/trunk:3"); |
| assertMergeInfo(new File(myBranchVcsRoot, "folder/folder1"), "/trunk:3*"); |
| |
| final List<SvnChangeList> changeListList = getTrunkChangeLists(); |
| |
| assertRevisions(changeListList, 3); |
| assertMergeResult(changeListList, SvnMergeInfoCache.MergeCheckResult.NOT_MERGED); |
| } |
| |
| @Test |
| public void testWhenInfoInRepo() throws Exception { |
| final File fullBranch = newFolder(myTempDirFixture.getTempDirPath(), "fullBranch"); |
| |
| createTwoFolderStructure(fullBranch); |
| // folder1 will be taken as branch wc root |
| checkOutFile(myBranchUrl + "/folder/folder1", myBranchVcsRoot); |
| |
| // rev 3 : f2 changed |
| editAndCommit(trunk, f2); |
| |
| // rev 4: record as merged into branch using full branch WC |
| recordMerge(fullBranch, myTrunkUrl, "-c", "3"); |
| commitFile(fullBranch); |
| updateFile(myBranchVcsRoot); |
| |
| final List<SvnChangeList> changeListList = getTrunkChangeLists(); |
| |
| assertRevisions(changeListList, 3); |
| assertMergeResult(changeListList.get(0), SvnMergeInfoCache.MergeCheckResult.MERGED); |
| } |
| |
| @Test |
| public void testMixedWorkingRevisions() throws Exception { |
| createOneFolderStructure(); |
| |
| // rev 3 |
| editAndCommit(trunk, f1); |
| |
| // rev 4: record non inheritable merge |
| setMergeInfo(myBranchVcsRoot, "/trunk:3"); |
| commitFile(myBranchVcsRoot); |
| // ! no update! |
| |
| assertMergeInfo(myBranchVcsRoot, "/trunk:3"); |
| |
| final Info f1info = myVcs.getInfo(new File(myBranchVcsRoot, "folder/f1.txt")); |
| assert f1info.getRevision().getNumber() == 2; |
| |
| final List<SvnChangeList> changeListList = getTrunkChangeLists(); |
| final SvnChangeList changeList = changeListList.get(0); |
| |
| assertMergeResult(changeList, SvnMergeInfoCache.MergeCheckResult.NOT_MERGED); |
| |
| // and after update |
| updateFile(myBranchVcsRoot); |
| myMergeChecker.clear(); |
| |
| assertMergeResult(changeList, SvnMergeInfoCache.MergeCheckResult.MERGED); |
| } |
| |
| private void createOneFolderStructure() throws InterruptedException, IOException { |
| trunk = newFolder(myTempDirFixture.getTempDirPath(), "trunk"); |
| folder = newFolder(trunk, "folder"); |
| f1 = newFile(folder, "f1.txt"); |
| f2 = newFile(folder, "f2.txt"); |
| waitTime(); |
| |
| importAndCheckOut(trunk); |
| } |
| |
| private void createTwoFolderStructure(File branchFolder) throws InterruptedException, IOException { |
| trunk = newFolder(myTempDirFixture.getTempDirPath(), "trunk"); |
| folder = newFolder(trunk, "folder"); |
| f1 = newFile(folder, "f1.txt"); |
| folder1 = newFolder(folder, "folder1"); |
| f2 = newFile(folder1, "f2.txt"); |
| waitTime(); |
| |
| importAndCheckOut(trunk, branchFolder); |
| } |
| |
| @NotNull |
| private List<SvnChangeList> getTrunkChangeLists() throws com.intellij.openapi.vcs.VcsException { |
| final CommittedChangesProvider<SvnChangeList, ChangeBrowserSettings> provider = myVcs.getCommittedChangesProvider(); |
| |
| return provider.getCommittedChanges(provider.createDefaultSettings(), new SvnRepositoryLocation(myTrunkUrl), 0); |
| } |
| |
| private void importAndCheckOut(@NotNull File trunk) throws IOException { |
| importAndCheckOut(trunk, myBranchVcsRoot); |
| } |
| |
| private void importAndCheckOut(@NotNull File trunk, @NotNull File branch) throws IOException { |
| runInAndVerifyIgnoreOutput("import", "-m", "test", trunk.getAbsolutePath(), myTrunkUrl); |
| runInAndVerifyIgnoreOutput("copy", "-m", "test", myTrunkUrl, myBranchUrl); |
| |
| FileUtil.delete(trunk); |
| checkOutFile(myTrunkUrl, trunk); |
| checkOutFile(myBranchUrl, branch); |
| } |
| |
| @NotNull |
| private VirtualFile editAndCommit(@NotNull File trunk, @NotNull File file) throws InterruptedException, IOException { |
| return editAndCommit(trunk, file, CONTENT1); |
| } |
| |
| @NotNull |
| private VirtualFile editAndCommit(@NotNull File trunk, @NotNull File file, @NotNull String content) |
| throws InterruptedException, IOException { |
| final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); |
| |
| return editAndCommit(trunk, vf, content); |
| } |
| |
| @NotNull |
| private VirtualFile editAndCommit(@NotNull File trunk, @NotNull VirtualFile file, @NotNull String content) |
| throws InterruptedException, IOException { |
| editFile(file, content); |
| commitFile(trunk); |
| |
| return file; |
| } |
| |
| private void editFile(@NotNull File file) throws InterruptedException { |
| editFile(file, CONTENT1); |
| } |
| |
| private void editFile(@NotNull File file, @NotNull String content) throws InterruptedException { |
| final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); |
| |
| editFile(vf, content); |
| } |
| |
| private void editFile(@NotNull VirtualFile file, @NotNull String content) throws InterruptedException { |
| VcsTestUtil.editFileInCommand(myProject, file, content); |
| waitTime(); |
| } |
| |
| private void assertMergeInfo(@NotNull File file, @NotNull String... values) throws SVNException { |
| // TODO: Replace with ClientFactory model |
| final SvnVcs vcs = SvnVcs.getInstance(myProject); |
| final SVNWCClient wcClient = vcs.getSvnKitManager().createWCClient(); |
| final SVNPropertyData data = wcClient.doGetProperty(file, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); |
| assert data != null && data.getValue() != null; |
| |
| boolean result = false; |
| |
| for (String value : values) { |
| result |= value.equals(data.getValue().getString()); |
| } |
| |
| assert result; |
| } |
| |
| private void assertMergeResult(@NotNull List<SvnChangeList> changeLists, @NotNull SvnMergeInfoCache.MergeCheckResult... mergeResults) |
| throws VcsException { |
| myOneShotMergeInfoHelper.prepare(); |
| |
| for (int i = 0; i < mergeResults.length; i++) { |
| SvnChangeList changeList = changeLists.get(i); |
| |
| assertMergeResult(changeList, mergeResults[i]); |
| assertMergeResultOneShot(changeList, mergeResults[i]); |
| } |
| } |
| |
| private void assertMergeResult(@NotNull SvnChangeList changeList, @NotNull SvnMergeInfoCache.MergeCheckResult mergeResult) { |
| assert mergeResult.equals(myMergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath())); |
| } |
| |
| private void assertMergeResultOneShot(@NotNull SvnChangeList changeList, @NotNull SvnMergeInfoCache.MergeCheckResult mergeResult) { |
| Assert.assertEquals(mergeResult, myOneShotMergeInfoHelper.checkList(changeList)); |
| } |
| |
| private static void assertRevisions(@NotNull List<SvnChangeList> changeLists, @NotNull int... revisions) { |
| for (int i = 0; i < revisions.length; i++) { |
| assert changeLists.get(i).getNumber() == revisions[i]; |
| } |
| } |
| |
| private void commitFile(@NotNull File file) throws IOException, InterruptedException { |
| runInAndVerifyIgnoreOutput("ci", "-m", "test", file.getAbsolutePath()); |
| waitTime(); |
| } |
| |
| private void updateFile(@NotNull File file) throws IOException, InterruptedException { |
| runInAndVerifyIgnoreOutput("up", file.getAbsolutePath()); |
| waitTime(); |
| } |
| |
| private void checkOutFile(@NotNull String url, @NotNull File directory) throws IOException { |
| runInAndVerifyIgnoreOutput("co", url, directory.getAbsolutePath()); |
| } |
| |
| private void setMergeInfo(@NotNull File file, @NotNull String value) throws IOException, InterruptedException { |
| runInAndVerifyIgnoreOutput("propset", "svn:mergeinfo", value, file.getAbsolutePath()); |
| waitTime(); |
| } |
| |
| private void merge(@NotNull File file, @NotNull String url, @NotNull String... revisions) throws IOException, InterruptedException { |
| merge(file, url, false, revisions); |
| } |
| |
| private void recordMerge(@NotNull File file, @NotNull String url, @NotNull String... revisions) throws IOException, InterruptedException { |
| merge(file, url, true, revisions); |
| } |
| |
| private void merge(@NotNull File file, @NotNull String url, boolean recordOnly, @NotNull String... revisions) |
| throws IOException, InterruptedException { |
| List<String> parameters = ContainerUtil.newArrayList(); |
| |
| parameters.add("merge"); |
| ContainerUtil.addAll(parameters, revisions); |
| parameters.add(url); |
| parameters.add(file.getAbsolutePath()); |
| if (recordOnly) { |
| parameters.add("--record-only"); |
| } |
| |
| runInAndVerifyIgnoreOutput(ArrayUtil.toObjectArray(parameters, String.class)); |
| waitTime(); |
| } |
| |
| @NotNull |
| private static File newFile(File parent, String name) throws IOException { |
| final File f1 = new File(parent, name); |
| f1.createNewFile(); |
| return f1; |
| } |
| |
| @NotNull |
| private static File newFolder(String parent, String name) throws InterruptedException { |
| final File trunk = new File(parent, name); |
| trunk.mkdir(); |
| waitTime(); |
| return trunk; |
| } |
| |
| @NotNull |
| private static File newFolder(File parent, String name) throws InterruptedException { |
| final File trunk = new File(parent, name); |
| trunk.mkdir(); |
| waitTime(); |
| return trunk; |
| } |
| |
| private static void waitTime() throws InterruptedException { |
| Thread.sleep(100); |
| } |
| } |