diff options
3 files changed, 176 insertions, 65 deletions
diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/op/DiscardChangesOperation.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/op/DiscardChangesOperation.java index c28491377d..b67fd80e27 100644 --- a/org.eclipse.egit.core/src/org/eclipse/egit/core/op/DiscardChangesOperation.java +++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/op/DiscardChangesOperation.java @@ -18,6 +18,7 @@ package org.eclipse.egit.core.op; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -131,6 +132,21 @@ public class DiscardChangesOperation implements IEGitOperation { } /** + * Retrieves the paths that will be reset. + * + * @return an unmodifiable map containing the paths per repository. + */ + public Map<Repository, Collection<String>> getPathsPerRepository() { + Map<Repository, Collection<String>> result = new HashMap<>(); + for (Map.Entry<Repository, Collection<String>> entry : pathsByRepository + .entrySet()) { + result.put(entry.getKey(), + Collections.unmodifiableCollection(entry.getValue())); + } + return Collections.unmodifiableMap(result); + } + + /** * Set the index stage to check out for conflicting files. Not compatible * with revision. * diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/team/actions/ReplaceActionsTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/team/actions/ReplaceActionsTest.java index 4d4793e457..a7daa7cbc2 100644 --- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/team/actions/ReplaceActionsTest.java +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/team/actions/ReplaceActionsTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2013 SAP AG and others. + * Copyright (c) 2012, 2019 SAP AG and others. * 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 @@ -53,8 +53,28 @@ public class ReplaceActionsTest extends LocalRepositoryTestCase { @Test public void testReplaceWithPrevious() throws Exception { + String initialContent = getTestFileContent(); touchAndSubmit(null); + assertThat(getTestFileContent(), not(initialContent)); + String menuLabel = util.getPluginLocalizedValue( + "ReplaceWithPreviousVersionAction.label"); + JobJoiner jobJoiner = JobJoiner.startListening( + JobFamilies.DISCARD_CHANGES, 30, TimeUnit.SECONDS); + clickReplaceWith(menuLabel); + jobJoiner.join(); + assertEquals(initialContent, getTestFileContent()); + } + + @Test + public void testReplaceWithPreviousChanged() throws Exception { String initialContent = getTestFileContent(); + touchAndSubmit(null); + String newContent = getTestFileContent(); + assertThat(newContent, not(initialContent)); + touch("Something else"); + String changedContent = getTestFileContent(); + assertThat(changedContent, not(initialContent)); + assertThat(changedContent, not(newContent)); String menuLabel = util .getPluginLocalizedValue( "ReplaceWithPreviousVersionAction.label"); @@ -63,8 +83,28 @@ public class ReplaceActionsTest extends LocalRepositoryTestCase { .shell(UIText.DiscardChangesAction_confirmActionTitle); executeReplace(confirm, UIText.DiscardChangesAction_discardChangesButtonText); - String replacedContent = getTestFileContent(); - assertThat(replacedContent, not(initialContent)); + assertEquals(initialContent, getTestFileContent()); + } + + @Test + public void testReplaceWithPreviousChangedClosed() throws Exception { + String initialContent = getTestFileContent(); + touchAndSubmit(null); + String newContent = getTestFileContent(); + assertThat(newContent, not(initialContent)); + touch("Something else"); + String changedContent = getTestFileContent(); + assertThat(changedContent, not(initialContent)); + assertThat(changedContent, not(newContent)); + String menuLabel = util.getPluginLocalizedValue( + "ReplaceWithPreviousVersionAction.label"); + clickReplaceWith(menuLabel); + SWTBotShell confirm = bot + .shell(UIText.DiscardChangesAction_confirmActionTitle); + confirm.close(); + TestUtil.processUIEvents(); + // Confirmation closed, nothing should have changed + assertEquals(changedContent, getTestFileContent()); } @Test @@ -104,9 +144,6 @@ public class ReplaceActionsTest extends LocalRepositoryTestCase { .getPluginLocalizedValue( "ReplaceWithPreviousVersionAction.label"); clickReplaceWith(menuLabel); - bot.shell(UIText.DiscardChangesAction_confirmActionTitle).bot() - .button(UIText.DiscardChangesAction_discardChangesButtonText) - .click(); SWTBotShell selectDialog = bot .shell(UIText.CommitSelectDialog_WindowTitle); assertEquals(2, selectDialog.bot().table().rowCount()); @@ -118,11 +155,6 @@ public class ReplaceActionsTest extends LocalRepositoryTestCase { assertEquals(contentAfterMerge, contentAfterClose); clickReplaceWith(menuLabel); - bot.shell(UIText.DiscardChangesAction_confirmActionTitle).bot() - .button(UIText.DiscardChangesAction_discardChangesButtonText) - .click(); - TestUtil.waitForJobs(100, 5000); - selectDialog = bot.shell(UIText.CommitSelectDialog_WindowTitle); // Select first parent, which should be the master commit SWTBotTable table = selectDialog.bot().table(); diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/DiscardChangesActionHandler.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/DiscardChangesActionHandler.java index c45d934225..2cfe03452c 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/DiscardChangesActionHandler.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/DiscardChangesActionHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2010, 2016 Roland Grunberg <rgrunber@redhat.com> and others + * Copyright (C) 2010, 2019 Roland Grunberg <rgrunber@redhat.com> and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -11,25 +11,28 @@ * Contributors: * Benjamin Muskalla (Tasktop Technologies Inc.) - support for model scoping * François Rey <eclipse.org_@_francois_._rey_._name> - handling of linked resources - * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 495777 + * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 495777, 546194 *******************************************************************************/ package org.eclipse.egit.ui.internal.actions; import java.text.MessageFormat; -import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.WorkspaceJob; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.egit.core.Activator; +import org.eclipse.egit.core.internal.indexdiff.IndexDiffCache; +import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry; +import org.eclipse.egit.core.internal.indexdiff.IndexDiffData; +import org.eclipse.egit.core.internal.job.JobUtil; import org.eclipse.egit.core.op.DiscardChangesOperation; -import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.JobFamilies; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.branch.LaunchFinder; @@ -37,6 +40,7 @@ import org.eclipse.egit.ui.internal.operations.GitScopeUtil; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.window.Window; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.ui.IWorkbenchPart; @@ -46,58 +50,42 @@ import org.eclipse.ui.IWorkbenchPart; */ public class DiscardChangesActionHandler extends RepositoryActionHandler { + private boolean hasDirectories; + @Override public Object execute(ExecutionEvent event) throws ExecutionException { // capture selection from active part as long as we have context mySelection = getSelection(event); try { IWorkbenchPart part = getPart(event); - String question = UIText.DiscardChangesAction_confirmActionMessage; - String launch = LaunchFinder - .getRunningLaunchConfiguration( - Arrays.asList(getRepositories()), null); - if (launch != null) { - question = MessageFormat.format(question, - "\n\n" + MessageFormat.format( //$NON-NLS-1$ - UIText.LaunchFinder_RunningLaunchMessage, - launch)); - } else { - question = MessageFormat.format(question, ""); //$NON-NLS-1$ - } - boolean performAction = openConfirmationDialog(event, question); - if (!performAction) { - return null; - } - final DiscardChangesOperation operation = createOperation(part, - event); - + DiscardChangesOperation operation = createOperation(part, event); if (operation == null) { return null; } - String jobname = UIText.DiscardChangesAction_discardChanges; - Job job = new WorkspaceJob(jobname) { - @Override - public IStatus runInWorkspace(IProgressMonitor monitor) { - try { - operation.execute(monitor); - } catch (CoreException e) { - return Activator.createErrorStatus( - e.getStatus().getMessage(), e); - } - return Status.OK_STATUS; + Map<Repository, Collection<String>> paths = operation + .getPathsPerRepository(); + if (haveChanges(paths)) { + String question = UIText.DiscardChangesAction_confirmActionMessage; + String launch = LaunchFinder + .getRunningLaunchConfiguration(paths.keySet(), null); + if (launch != null) { + question = MessageFormat.format(question, + "\n\n" + MessageFormat.format( //$NON-NLS-1$ + UIText.LaunchFinder_RunningLaunchMessage, + launch)); + } else { + question = MessageFormat.format(question, ""); //$NON-NLS-1$ } - - @Override - public boolean belongsTo(Object family) { - if (JobFamilies.DISCARD_CHANGES.equals(family)) { - return true; - } - return super.belongsTo(family); + if (!openConfirmationDialog(event, question)) { + return null; } - }; - job.setUser(true); - job.setRule(operation.getSchedulingRule()); - job.schedule(); + } else if (LaunchFinder.shouldCancelBecauseOfRunningLaunches( + paths.keySet(), null)) { + return null; + } + JobUtil.scheduleUserWorkspaceJob(operation, + UIText.DiscardChangesAction_discardChanges, + JobFamilies.DISCARD_CHANGES); return null; } finally { // cleanup mySelection to avoid side effects later after execution @@ -105,6 +93,82 @@ public class DiscardChangesActionHandler extends RepositoryActionHandler { } } + private boolean haveChanges(Map<Repository, Collection<String>> paths) { + IndexDiffCache cache = Activator.getDefault().getIndexDiffCache(); + for (Map.Entry<Repository, Collection<String>> entry : paths + .entrySet()) { + Repository repo = entry.getKey(); + Assert.isNotNull(repo); + IndexDiffCacheEntry indexDiff = cache.getIndexDiffCacheEntry(repo); + if (indexDiff == null) { + return true; // No info, assume worst case + } + IndexDiffData diff = indexDiff.getIndexDiff(); + if (diff == null || hasChanges(diff, entry.getValue())) { + return true; + } + } + return false; + } + + private boolean hasChanges(@NonNull IndexDiffData diff, + Collection<String> paths) { + Set<String> repoPaths = new HashSet<>(paths); + // Untracked files are ignored and won't be removed. + if (repoPaths.contains("")) { //$NON-NLS-1$ + // Working tree root included + return diff.hasChanges(); + } + // Do the directories later to avoid having to do all the (potentially + // expensive) substrings if a plain file already matches. + if (containsAny(repoPaths, diff.getAdded()) + || containsAny(repoPaths, diff.getChanged()) + || containsAny(repoPaths, diff.getModified()) + || containsAny(repoPaths, diff.getRemoved())) { + return true; + } + if (hasDirectories) { + return containsAnyDirectory(repoPaths, diff.getAdded()) + || containsAnyDirectory(repoPaths, diff.getChanged()) + || containsAnyDirectory(repoPaths, diff.getModified()) + || containsAnyDirectory(repoPaths, diff.getRemoved()); + } + return false; + } + + private boolean containsAny(Set<String> repoPaths, + Collection<String> files) { + return files.stream().anyMatch(repoPaths::contains); + } + + private boolean containsAnyDirectory(Set<String> repoPaths, + Collection<String> files) { + String lastDirectory = null; + for (String file : files) { + int j = file.lastIndexOf('/'); + if (j <= 0) { + continue; + } + String directory = file.substring(0, j); + String withTerminator = directory + '/'; + if (lastDirectory != null + && lastDirectory.startsWith(withTerminator)) { + continue; + } + if (repoPaths.contains(directory)) { + return true; + } + lastDirectory = withTerminator; + for (int i = directory.indexOf('/'); i > 0; i = directory.indexOf( + '/', i + 1)) { + if (repoPaths.contains(directory.substring(0, i))) { + return true; + } + } + } + return false; + } + private boolean openConfirmationDialog(ExecutionEvent event, String question) throws ExecutionException { MessageDialog dlg = new MessageDialog(getShell(event), @@ -131,7 +195,6 @@ public class DiscardChangesActionHandler extends RepositoryActionHandler { private DiscardChangesOperation createOperation(IWorkbenchPart part, ExecutionEvent event) throws ExecutionException { - IResource[] selectedResources = gatherResourceToOperateOn(event); String revision; try { @@ -149,9 +212,9 @@ public class DiscardChangesActionHandler extends RepositoryActionHandler { // cancels the scope operation return null; } - + hasDirectories = Stream.of(resourcesInScope) + .anyMatch(rsc -> rsc.getType() != IResource.FILE); return new DiscardChangesOperation(resourcesInScope, revision); - } /** |