Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.egit.core/src/org/eclipse/egit/core/op/DiscardChangesOperation.java16
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/team/actions/ReplaceActionsTest.java54
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/DiscardChangesActionHandler.java171
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);
-
}
/**

Back to the top