Skip to main content
summaryrefslogtreecommitdiffstats
blob: 69087220d04d503cbd4c6aae558312df96e3c28a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package org.eclipse.team.internal.ccvs.ui.sync;

/*
 * (c) Copyright IBM Corp. 2000, 2002.
 * All Rights Reserved.
 */
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.IRemoteSyncElement;
import org.eclipse.team.internal.ccvs.core.resources.CVSRemoteSyncElement;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ccvs.ui.RepositoryManager;
import org.eclipse.team.ui.sync.ChangedTeamContainer;
import org.eclipse.team.ui.sync.ITeamNode;
import org.eclipse.team.ui.sync.SyncSet;
import org.eclipse.team.ui.sync.SyncView;
import org.eclipse.team.ui.sync.TeamFile;

public class ForceCommitSyncAction extends MergeAction {
	public ForceCommitSyncAction(CVSSyncCompareInput model, ISelectionProvider sp, String label, Shell shell) {
		super(model, sp, label, shell);
	}

	protected SyncSet run(SyncSet syncSet, IProgressMonitor monitor) {
		// If there is a conflict in the syncSet, we need to prompt the user before proceeding.
		if (syncSet.hasConflicts() || syncSet.hasIncomingChanges()) {
			switch (promptForConflicts(syncSet)) {
				case 0:
					// Yes, synchronize conflicts as well
					break;
				case 1:
					// No, only synchronize non-conflicting changes.
					syncSet.removeConflictingNodes();
					syncSet.removeIncomingNodes();
					break;
				case 2:
				default:
					// Cancel
					return null;
			}	
		}
		ITeamNode[] changed = syncSet.getChangedNodes();
		if (changed.length == 0) {
			return syncSet;
		}
		IResource[] changedResources = new IResource[changed.length];
		List additions = new ArrayList();
		List deletions = new ArrayList();
		List toMerge = new ArrayList();
		List incoming = new ArrayList();

		// A list of diff elements in the sync set which are incoming folder additions
		List parentCreationElements = new ArrayList();
		// A list of diff elements in the sync set which are folder conflicts
		List parentConflictElements = new ArrayList();
		
		for (int i = 0; i < changed.length; i++) {
			changedResources[i] = changed[i].getResource();
			int kind = changed[i].getKind();
			IResource resource = changed[i].getResource();
			IDiffContainer parent = changed[i].getParent();
			if (parent != null) {
				int parentKind = changed[i].getParent().getKind();
				if (((parentKind & Differencer.CHANGE_TYPE_MASK) == Differencer.ADDITION) &&
					((parentKind & Differencer.DIRECTION_MASK) == ITeamNode.INCOMING)) {
					parentCreationElements.add(parent);
				} else if ((parentKind & Differencer.DIRECTION_MASK) == ITeamNode.CONFLICTING) {
					parentConflictElements.add(parent);
				}
			}
			switch (kind & Differencer.DIRECTION_MASK) {
				case ITeamNode.INCOMING:
					// Incoming change. Make it outgoing before committing.
					incoming.add(changed[i]);
					break;
				case ITeamNode.OUTGOING:
					switch (kind & Differencer.CHANGE_TYPE_MASK) {
						case Differencer.ADDITION:
							// Outgoing addition. 'add' it before committing.
							additions.add(resource);
							break;
						case Differencer.DELETION:
							// Outgoing deletion. 'delete' it before committing.
							deletions.add(resource);
							break;
						case Differencer.CHANGE:
							// Outgoing change. Just commit it.
							break;
					}
					break;
				case ITeamNode.CONFLICTING:
					if (changed[i] instanceof TeamFile) {
						toMerge.add(((TeamFile)changed[i]).getMergeResource().getSyncElement());
					}
					break;
			}
		}
		try {
			RepositoryManager manager = CVSUIPlugin.getPlugin().getRepositoryManager();
			String comment = promptForComment(manager);
			if (comment == null) {
				// User cancelled. Remove the nodes from the sync set.
				return null;
			}
			if (parentCreationElements.size() > 0) {
				// If a node has a parent that is an incoming folder creation, we have to 
				// create that folder locally and set its sync info before we can get the
				// node itself. We must do this for all incoming folder creations (recursively)
				// in the case where there are multiple levels of incoming folder creations.
				Iterator it = parentCreationElements.iterator();
				while (it.hasNext()) {
					makeInSync((IDiffElement)it.next());
				}				
			}
			if (parentConflictElements.size() > 0) {
				// If a node has a parent that is a folder conflict, that means that the folder
				// exists locally but has no sync info. In order to get the node, we have to 
				// create the sync info for the folder (and any applicable parents) before we
				// get the node itself.
				Iterator it = parentConflictElements.iterator();
				while (it.hasNext()) {
					makeInSync((IDiffElement)it.next());
				}				
			}

			// Make any incoming file changes or deletions into outgoing changes before committing.
			Iterator it = incoming.iterator();
			while (it.hasNext()) {
				ITeamNode node = (ITeamNode)it.next();
				if (node instanceof TeamFile) {
					CVSRemoteSyncElement element = (CVSRemoteSyncElement)((TeamFile)node).getMergeResource().getSyncElement();
					element.makeOutgoing(monitor);
				}
			}
			
			if (additions.size() != 0) {
				manager.add((IResource[])additions.toArray(new IResource[0]), monitor);
			}
			if (deletions.size() != 0) {
				manager.delete((IResource[])deletions.toArray(new IResource[0]), monitor);
			}
			if (toMerge.size() != 0) {
				manager.merged((IRemoteSyncElement[])toMerge.toArray(new IRemoteSyncElement[0]));
			}
			manager.commit(changedResources, comment, monitor);
			
			it = incoming.iterator();
			while (it.hasNext()) {
				ITeamNode node = (ITeamNode)it.next();
				if (node instanceof ChangedTeamContainer) {
					CVSRemoteSyncElement element = (CVSRemoteSyncElement)((ChangedTeamContainer)node).getMergeResource().getSyncElement();
					element.makeIncoming(monitor);
					element.getLocal().delete(true, monitor);
				}
			}
			
		} catch (final TeamException e) {
			getShell().getDisplay().syncExec(new Runnable() {
				public void run() {
					ErrorDialog.openError(getShell(), null, null, e.getStatus());
				}
			});
			return null;
		} catch (final CoreException e) {
			getShell().getDisplay().syncExec(new Runnable() {
				public void run() {
					ErrorDialog.openError(getShell(), Policy.bind("simpleInternal"), Policy.bind("internal"), e.getStatus());
					CVSUIPlugin.log(e.getStatus());
				}
			});
			return null;
		}
		return syncSet;
	}
	
	protected void makeInSync(IDiffElement parentElement) throws TeamException {
		// Recursively make the parent element (and its parents) in sync.
		// Walk up and find the parents which need to be made in sync too. (For
		// each parent that doesn't already have sync info).
		Vector v = new Vector();
		int parentKind = parentElement.getKind();
		while (((parentKind & Differencer.CHANGE_TYPE_MASK) == Differencer.ADDITION) &&
			((parentKind & Differencer.DIRECTION_MASK) == ITeamNode.INCOMING) ||
			 ((parentKind & Differencer.DIRECTION_MASK) == ITeamNode.CONFLICTING)) {
			v.add(0, parentElement);
			parentElement = parentElement.getParent();
			parentKind = parentElement == null ? 0 : parentElement.getKind();
		}
		Iterator parentIt = v.iterator();
		while (parentIt.hasNext()) {
			IDiffElement next = (IDiffElement)parentIt.next();
			if (next instanceof ChangedTeamContainer) {
				CVSRemoteSyncElement syncElement = (CVSRemoteSyncElement)((ChangedTeamContainer)next).getMergeResource().getSyncElement();
				// Create the sync info
				syncElement.makeInSync(new NullProgressMonitor());
			}
		}
	}

	protected boolean isEnabled(ITeamNode node) {
		// The force commit action is enabled only for conflicting and incoming changes
		SyncSet set = new SyncSet(new StructuredSelection(node));
		return (set.hasIncomingChanges() || set.hasConflicts());
	}	
	
	/**
	 * Prompts the user to determine how conflicting changes should be handled.
	 * @return 0 to sync conflicts, 1 to sync all non-conflicts, 2 to cancel
	 */
	protected int promptForConflicts(SyncSet syncSet) {
		String[] buttons = new String[] {IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL};
		String question = Policy.bind("CommitSyncAction.questionRelease");
		String title = Policy.bind("CommitSyncAction.titleRelease");
		String[] tips = new String[] {
			Policy.bind("CommitSyncAction.releaseAll"),
			Policy.bind("CommitSyncAction.releasePart"),
			Policy.bind("CommitSyncAction.cancelRelease")
		};
		Shell shell = getShell();
		final ToolTipMessageDialog dialog = new ToolTipMessageDialog(shell, title, null, question, MessageDialog.QUESTION, buttons, tips, 0);
		shell.getDisplay().syncExec(new Runnable() {
			public void run() {
				dialog.open();
			}
		});
		return dialog.getReturnCode();
	}
	
	/**
	 * Prompts the user for a release comment.
	 * @return the comment, or null to cancel
	 */
	protected String promptForComment(RepositoryManager manager) {
		return manager.promptForComment(getShell());
	}

	protected void removeNonApplicableNodes(SyncSet set, int syncMode) {
		set.removeOutgoingNodes();
		if (syncMode != SyncView.SYNC_BOTH) {
			set.removeIncomingNodes();
		}
	}
}

Back to the top