From 4fcd0f9d624f9fe738c47c9989c161ffd899169e Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Fri, 18 Dec 2015 16:30:22 +0300 Subject: [PATCH] [lvcs]: implement rollback to Label from local history * add label ID to identify label change; * create a local history model; * implement revert method by label; * inform if failed; * tests added; --- .../src/com/intellij/history/Label.java | 8 ++ .../com/intellij/history/core/LabelImpl.java | 2 + .../history/core/LocalHistoryFacade.java | 6 + .../core/revisions/ChangeRevision.java | 4 + .../history/integration/LocalHistoryImpl.java | 27 +++++ .../history/integration/LocalHistoryUtil.java | 51 ++++++++ .../revertion/HistoryReverterToLabelTest.java | 110 ++++++++++++++++++ .../patch/formove/UndoApplyPatchDialog.java | 2 + 8 files changed, 210 insertions(+) create mode 100644 platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryUtil.java create mode 100644 platform/platform-tests/testSrc/com/intellij/history/integration/revertion/HistoryReverterToLabelTest.java diff --git a/platform/lvcs-api/src/com/intellij/history/Label.java b/platform/lvcs-api/src/com/intellij/history/Label.java index f31271e60f6b..95ef54ace083 100644 --- a/platform/lvcs-api/src/com/intellij/history/Label.java +++ b/platform/lvcs-api/src/com/intellij/history/Label.java @@ -18,10 +18,18 @@ package com.intellij.history; public interface Label { Label NULL_INSTANCE = new Label() { + + @Override + public long getLabelChangeId() { + return -1; + } + public ByteContent getByteContent(String path) { return null; } }; + long getLabelChangeId(); + ByteContent getByteContent(String path); } diff --git a/platform/lvcs-impl/src/com/intellij/history/core/LabelImpl.java b/platform/lvcs-impl/src/com/intellij/history/core/LabelImpl.java index 3be56c87c3ae..c75131c62f08 100644 --- a/platform/lvcs-impl/src/com/intellij/history/core/LabelImpl.java +++ b/platform/lvcs-impl/src/com/intellij/history/core/LabelImpl.java @@ -19,5 +19,7 @@ import com.intellij.history.ByteContent; import com.intellij.history.core.tree.RootEntry; public interface LabelImpl { + long getLabelChangeId(); + ByteContent getByteContent(RootEntry root, String path); } diff --git a/platform/lvcs-impl/src/com/intellij/history/core/LocalHistoryFacade.java b/platform/lvcs-impl/src/com/intellij/history/core/LocalHistoryFacade.java index c70fd37346f7..9767da69818f 100644 --- a/platform/lvcs-impl/src/com/intellij/history/core/LocalHistoryFacade.java +++ b/platform/lvcs-impl/src/com/intellij/history/core/LocalHistoryFacade.java @@ -105,6 +105,12 @@ public class LocalHistoryFacade { private LabelImpl putLabel(@NotNull final PutLabelChange c) { addChange(c); return new LabelImpl() { + + @Override + public long getLabelChangeId() { + return c.getId(); + } + @Override public ByteContent getByteContent(RootEntry root, String path) { return getByteContentBefore(root, path, c); diff --git a/platform/lvcs-impl/src/com/intellij/history/core/revisions/ChangeRevision.java b/platform/lvcs-impl/src/com/intellij/history/core/revisions/ChangeRevision.java index c72fc77bb091..f47ca0b8a970 100644 --- a/platform/lvcs-impl/src/com/intellij/history/core/revisions/ChangeRevision.java +++ b/platform/lvcs-impl/src/com/intellij/history/core/revisions/ChangeRevision.java @@ -108,4 +108,8 @@ public class ChangeRevision extends Revision { public String toString() { return getClass().getSimpleName() + ": " + myChangeToRevert; } + + public boolean containsChangeWithId(long id) { + return myChangeToRevert.getId() == id; + } } \ No newline at end of file diff --git a/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryImpl.java b/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryImpl.java index 238d29853f83..ee6b80f85b4b 100644 --- a/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryImpl.java +++ b/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryImpl.java @@ -19,6 +19,9 @@ package com.intellij.history.integration; import com.intellij.history.*; import com.intellij.history.core.*; import com.intellij.history.core.tree.RootEntry; +import com.intellij.history.integration.ui.models.DirectoryHistoryDialogModel; +import com.intellij.history.integration.ui.models.EntireFileHistoryDialogModel; +import com.intellij.history.integration.ui.models.HistoryDialogModel; import com.intellij.history.utils.LocalHistoryLog; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; @@ -38,8 +41,11 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.io.File; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; +import static com.intellij.history.integration.LocalHistoryUtil.findRevisionIndexToRevert; + public class LocalHistoryImpl extends LocalHistory implements ApplicationComponent { private ChangeList myChangeList; private LocalHistoryFacade myVcs; @@ -183,6 +189,11 @@ public class LocalHistoryImpl extends LocalHistory implements ApplicationCompone private Label label(final LabelImpl impl) { return new Label() { + @Override + public long getLabelChangeId() { + return impl.getLabelChangeId(); + } + @Override public ByteContent getByteContent(final String path) { return ApplicationManager.getApplication().runReadAction(new Computable() { @@ -237,5 +248,21 @@ public class LocalHistoryImpl extends LocalHistory implements ApplicationCompone @Override public void revertToLabel(@NotNull Project project, @NotNull VirtualFile f, @NotNull Label label) { + HistoryDialogModel dirHistoryModel = f.isDirectory() + ? new DirectoryHistoryDialogModel(project, myGateway, myVcs, f) + : new EntireFileHistoryDialogModel(project, myGateway, myVcs, f); + int leftRev = findRevisionIndexToRevert(dirHistoryModel, label); + if (leftRev < 0) { + LocalHistoryLog.LOG.warn( + String.format("Couldn't find label revision. try to Use local history dialog for %s and perform revert manually.", f.getName())); + return; + } + try { + dirHistoryModel.selectRevisions(-1, leftRev - 1); //-1 because we should revert all changes up to previous one, but not label-related. + dirHistoryModel.createReverter().revert(); + } + catch (IOException e) { + LocalHistoryLog.LOG.error(String.format("Couldn't revert %s to local history label.", f.getName()), e); + } } } diff --git a/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryUtil.java b/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryUtil.java new file mode 100644 index 000000000000..2bc063d6e613 --- /dev/null +++ b/platform/lvcs-impl/src/com/intellij/history/integration/LocalHistoryUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2016 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 com.intellij.history.integration; + +import com.intellij.history.Label; +import com.intellij.history.core.revisions.ChangeRevision; +import com.intellij.history.core.revisions.Revision; +import com.intellij.history.integration.ui.models.HistoryDialogModel; +import com.intellij.history.integration.ui.models.RevisionItem; +import com.intellij.openapi.util.Condition; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class LocalHistoryUtil { + + static int findRevisionIndexToRevert(@NotNull HistoryDialogModel dirHistoryModel, @NotNull Label label) { + List revs = dirHistoryModel.getRevisions(); + for (int i = 0; i < revs.size(); i++) { + final RevisionItem rev = revs.get(i); + if (isLabelRevision(rev, label)) { + return i; + } + } + return -1; + } + + static boolean isLabelRevision(@NotNull RevisionItem rev, @NotNull Label label) { + final long targetChangeId = label.getLabelChangeId(); + return ContainerUtil.exists(rev.labels, new Condition() { + @Override + public boolean value(Revision revision) { + return revision instanceof ChangeRevision && ((ChangeRevision)revision).containsChangeWithId(targetChangeId); + } + }); + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/history/integration/revertion/HistoryReverterToLabelTest.java b/platform/platform-tests/testSrc/com/intellij/history/integration/revertion/HistoryReverterToLabelTest.java new file mode 100644 index 000000000000..885d8970596b --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/history/integration/revertion/HistoryReverterToLabelTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2016 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 com.intellij.history.integration.revertion; + +import com.intellij.history.Label; +import com.intellij.history.LocalHistory; +import com.intellij.history.integration.IntegrationTestCase; +import com.intellij.openapi.vfs.VirtualFile; + +public class HistoryReverterToLabelTest extends IntegrationTestCase { + + public void testFileCreation() throws Exception { + createChildData(myRoot, "first.txt"); + final LocalHistory localHistory = LocalHistory.getInstance(); + final Label testLabel = localHistory.putSystemLabel(myProject, "testLabel"); + createChildData(myRoot, "foo.txt"); + localHistory.revertToLabel(myProject, myRoot, testLabel); + assertNull(myRoot.findChild("foo.txt")); + assertNotNull(myRoot.findChild("first.txt")); + } + + public void testFileDeletion() throws Exception { + VirtualFile f = createChildData(myRoot, "foo.txt"); + setBinaryContent(f, new byte[]{123}, -1, 4000, this); + final LocalHistory localHistory = LocalHistory.getInstance(); + final Label testLabel = localHistory.putSystemLabel(myProject, "testLabel"); + delete(f); + localHistory.revertToLabel(myProject, myRoot, testLabel); + f = myRoot.findChild("foo.txt"); + assertNotNull(f); + assertEquals(123, f.contentsToByteArray()[0]); + assertEquals(4000, f.getTimeStamp()); + } + + public void testFileDeletionWithContent() throws Exception { + VirtualFile f = createChildData(myRoot, "foo.txt"); + final LocalHistory localHistory = LocalHistory.getInstance(); + final Label testLabel = localHistory.putSystemLabel(myProject, "testLabel"); + setBinaryContent(f, new byte[]{123}, -1, 4000, this); + delete(f); + localHistory.revertToLabel(myProject, myRoot, testLabel); + f = myRoot.findChild("foo.txt"); + assertNotNull(f); + assertEquals(0, f.contentsToByteArray().length); + } + + public void testParentAndChildRename() throws Exception { + VirtualFile dir = createChildDirectory(myRoot, "dir"); + VirtualFile f = createChildData(dir, "foo.txt"); + int modificationStamp = -1; + setBinaryContent(f, new byte[]{123}, modificationStamp, 4000, this); + final LocalHistory localHistory = LocalHistory.getInstance(); + final Label testLabel1 = localHistory.putSystemLabel(myProject, "testLabel"); + rename(dir, "dir2"); + final Label testLabel2 = localHistory.putSystemLabel(myProject, "testLabel"); + rename(f, "bar.txt"); + + localHistory.revertToLabel(myProject, myRoot, testLabel2); + + assertNotNull(myRoot.findChild("dir2")); + dir = myRoot.findChild("dir2"); + + assert dir != null; + assertNull(dir.findChild("bar.txt")); + f = dir.findChild("foo.txt"); + assertNotNull(f); + assertEquals(123, f.contentsToByteArray()[0]); + assertEquals(4000, f.getTimeStamp()); + + localHistory.revertToLabel(myProject, myRoot, testLabel1); + assertNull(myRoot.findChild("dir2")); + dir = myRoot.findChild("dir"); + + assert dir != null; + assertNull(dir.findChild("bar.txt")); + f = dir.findChild("foo.txt"); + assertNotNull(f); + assertEquals(123, f.contentsToByteArray()[0]); + assertEquals(4000, f.getTimeStamp()); + } + + public void testRevertContentChange() throws Exception { + VirtualFile f = createChildData(myRoot, "foo.txt"); + int modificationStamp1 = -1; + setBinaryContent(f, new byte[]{1}, modificationStamp1, 1000, this); + final LocalHistory localHistory = LocalHistory.getInstance(); + final Label testLabel = localHistory.putSystemLabel(myProject, "testLabel"); + int modificationStamp = -1; + setBinaryContent(f, new byte[]{2}, modificationStamp, 2000, this); + setBinaryContent(f, new byte[]{3}, modificationStamp, 3000, this); + localHistory.revertToLabel(myProject, myRoot, testLabel); + f = myRoot.findChild("foo.txt"); + assertNotNull(f); + assertEquals(1, f.contentsToByteArray()[0]); + assertEquals(1000, f.getTimeStamp()); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/UndoApplyPatchDialog.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/UndoApplyPatchDialog.java index 617767072842..1306c7c5bc63 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/UndoApplyPatchDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/UndoApplyPatchDialog.java @@ -16,6 +16,7 @@ package com.intellij.openapi.diff.impl.patch.formove; import com.intellij.history.Label; +import com.intellij.history.LocalHistory; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; @@ -70,6 +71,7 @@ class UndoApplyPatchDialog extends DialogWrapper { @Override protected void doOKAction() { super.doOKAction(); + LocalHistory.getInstance().revertToLabel(myProject, myProject.getBaseDir(), myBeforeLabel); } static void rollbackApplyPatch(@NotNull Project project, @NotNull List filePaths,