Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: a5d1a171ee77bd11cfbbd28bfa210c48c62bc5ce (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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 *
 * 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
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.subscriber;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.mapping.provider.SynchronizationScopeManager;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.core.synchronize.SyncInfoSet;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.Command;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ccvs.ui.mappings.ChangeSetComparator;
import org.eclipse.team.internal.ccvs.ui.operations.*;
import org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager;
import org.eclipse.team.internal.core.subscribers.ChangeSet;
import org.eclipse.team.internal.ui.Utils;
import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration;

public class WorkspaceCommitOperation extends CVSSubscriberOperation {
	
	private String comment;
	private SyncInfoSet syncSet;
	private boolean override;

	public WorkspaceCommitOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements, boolean override) {
		super(configuration, elements);
		this.override = override;
	}

	@Override
	protected String getErrorTitle() {
		return CVSUIMessages.CommitAction_commitFailed; 
	}
	
	@Override
	protected String getJobName() {
		SyncInfoSet syncSet = getSyncInfoSet();
		return NLS.bind(CVSUIMessages.CommitAction_jobName, new String[] { Integer.valueOf(syncSet.size()).toString() }); 
	}
	
	@Override
	public boolean shouldRun() {
		SyncInfoSet set = getSyncInfoSet();
		return !set.isEmpty();
	}
	
	@Override
	protected SyncInfoSet getSyncInfoSet() {
		if (syncSet == null) {
			syncSet = super.getSyncInfoSet();
			if (!promptForConflictHandling(syncSet)) {
				syncSet.clear();
				return syncSet;
			}
			try {
				if (!promptForUnaddedHandling(syncSet)) {
					syncSet.clear();
					return syncSet;
				}
			} catch (CVSException e) {
				Utils.handle(e);
				syncSet.clear();
			}
		}
		return syncSet;
	}
	
	protected boolean promptForConflictHandling(SyncInfoSet syncSet) {
		if (syncSet.hasConflicts() || syncSet.hasIncomingChanges()) {
			if (override) {
				// If overriding, prompt to ensure that is what the user wants
				switch (promptForConflicts(syncSet)) {
					case 0:
						// Yes, synchronize conflicts as well
						break;
					case 1:
						// No, stop here
						return false;
					case 2:
					default:
						// Cancel
						return false;
				}	
			} else {
				// If there is a conflict in the syncSet, remove from sync set.
				syncSet.removeConflictingNodes();
				syncSet.removeIncomingNodes();
			}
		}
		return true;
	}
	
	@Override
	public void runWithProjectRule(IProject project, SyncInfoSet syncSet, IProgressMonitor monitor) throws TeamException {
		
		final SyncInfo[] changed = syncSet.getSyncInfos();
		if (changed.length == 0) return;
		
		// A list of files to be committed
		final List<IResource> commits = new ArrayList<>();
		// New resources that are not yet under CVS control and need a "cvs add"
		final List<IResource> additions = new ArrayList<>();
		// A list of incoming or conflicting file changes to be made outgoing changes
		final List<SyncInfo> makeOutgoing = new ArrayList<>();
		// A list of out-of-sync folders that must be made in-sync
		final List<SyncInfo> makeInSync = new ArrayList<>();
		
		for (SyncInfo changedNode : changed) {
			int kind = changedNode.getKind();
			IResource resource = changedNode.getLocal();
			
			// Any parent folders should be made in-sync.
			// Steps will be taken after the commit to prune any empty folders
			SyncInfo parent = getParent(changedNode);
			if (parent != null) {
				if (isOutOfSync(parent)) {
					makeInSync.add(parent);
				}
			}
			
			if (resource.getType() == IResource.FILE) {
				// By default, all files are committed
				commits.add(resource);
				// Determine what other work needs to be done for the file
				switch (kind & SyncInfo.DIRECTION_MASK) {
					case SyncInfo.INCOMING:
						// Convert the incoming change to an outgoing change
						makeOutgoing.add(changedNode);
						break;
					case SyncInfo.OUTGOING:
						switch (kind & SyncInfo.CHANGE_MASK) {
							case SyncInfo.ADDITION:
								// Outgoing addition. 'add' it before committing.
								if (!isAdded(resource))
									additions.add(resource);
								break;
							case SyncInfo.DELETION:
								// Outgoing deletion is handled by move/delete
								// hook and EclipseSynchronizer
								break;
							case SyncInfo.CHANGE:
								// Outgoing change. Just commit it.
								break;
						}
						break;
					case SyncInfo.CONFLICTING:
						// Convert the conflicting change to an outgoing change
						makeOutgoing.add(changedNode);
						break;
				}
			} else {
				if (((kind & SyncInfo.DIRECTION_MASK) == SyncInfo.OUTGOING)
					&& ((kind & SyncInfo.CHANGE_MASK) == SyncInfo.ADDITION)) {
						// Outgoing folder additions must be added
						additions.add(changedNode.getLocal());
				} else if (isOutOfSync(changedNode)) {
					// otherwise, make any out-of-sync folders in-sync using the remote info
					makeInSync.add(changedNode);
				}
			}
		}
		monitor.beginTask(null, 200);
		
		if (makeInSync.size() > 0) {
			makeInSync(makeInSync.toArray(new SyncInfo[makeInSync.size()]), Policy.subMonitorFor(monitor, 25));			
		}

		if (makeOutgoing.size() > 0) {
			makeOutgoing(makeOutgoing.toArray(new SyncInfo[makeInSync.size()]), Policy.subMonitorFor(monitor, 25));			
		}

		if (!additions.isEmpty()) {
			add(project, additions.toArray(new IResource[0]), Policy.subMonitorFor(monitor, 50));
		}
		commit(project, commits.toArray(new IResource[commits.size()]), Policy.subMonitorFor(monitor, 100));		
	}	
	
	private void commit(final IProject project, IResource[] commits, IProgressMonitor monitor) throws TeamException {
		try {
			CommitOperation commitOperation = new CommitOperation(getPart(), RepositoryProviderOperation.asResourceMappers(commits),
					new Command.LocalOption[0], comment) {
				@Override
				protected ResourceMappingContext getResourceMappingContext() {
					return new SingleProjectSubscriberContext(CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber(), false, project);
				}
				@Override
				protected SynchronizationScopeManager createScopeManager(boolean consultModels) {
					return new SingleProjectScopeManager(getJobName(), getSelectedMappings(), getResourceMappingContext(), consultModels, project);
				}
			};
			commitOperation
						.run(monitor);
		} catch (InvocationTargetException e) {
			throw TeamException.asTeamException(e);
		} catch (InterruptedException e) {
			throw new OperationCanceledException();
		}
	}

	private void add(final IProject project, IResource[] additions, IProgressMonitor monitor) throws TeamException {
		try {
			new AddOperation(getPart(), RepositoryProviderOperation.asResourceMappers(additions)) {
				@Override
				protected ResourceMappingContext getResourceMappingContext() {
					return new SingleProjectSubscriberContext(CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber(), false, project);
				}
				@Override
				protected SynchronizationScopeManager createScopeManager(boolean consultModels) {
					return new SingleProjectScopeManager(getJobName(), getSelectedMappings(), getResourceMappingContext(), consultModels, project);
				}
			}.run(monitor);
		} catch (InvocationTargetException e1) {
			throw TeamException.asTeamException(e1);
		} catch (InterruptedException e1) {
			throw new OperationCanceledException();
		}
	}

	/**
	 * Prompts the user to determine how conflicting changes should be handled.
	 * Note: This method is designed to be overridden by test cases.
	 * @return 0 to sync conflicts, 1 to sync all non-conflicts, 2 to cancel
	 */
	protected int promptForConflicts(SyncInfoSet syncSet) {
		String[] buttons = new String[] {IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL};
		String question = CVSUIMessages.CommitSyncAction_questionRelease; 
		String title = CVSUIMessages.CommitSyncAction_titleRelease; 
		String[] tips = new String[] {
			CVSUIMessages.CommitSyncAction_releaseAll, 
			CVSUIMessages.CommitSyncAction_releasePart, 
			CVSUIMessages.CommitSyncAction_cancelRelease
		};
		Shell shell = getShell();
		final ToolTipMessageDialog dialog = new ToolTipMessageDialog(shell, title, null, question, MessageDialog.QUESTION, buttons, tips, 0);
		shell.getDisplay().syncExec(() -> dialog.open());
		return dialog.getReturnCode();
	}
		
	/**
	 * Prompts the user for a release comment.
	 * Note: This method is designed to be overridden by test cases.
	 * @return the comment, or null to cancel
	 */
	protected String promptForComment(RepositoryManager manager, IResource[] resourcesToCommit) {
		String proposedComment = getProposedComment(resourcesToCommit);
		return manager.promptForComment(getShell(), resourcesToCommit, proposedComment);
	}

	private String getProposedComment(IResource[] resourcesToCommit) {
		StringBuffer comment = new StringBuffer();
		ChangeSet[] sets = CVSUIPlugin.getPlugin().getChangeSetManager().getSets();
		Arrays.sort(sets, new ChangeSetComparator());
		int numMatchedSets = 0;
		for (ChangeSet set : sets) {
			if (containsOne(set, resourcesToCommit)) {
				if(numMatchedSets > 0) comment.append(System.getProperty("line.separator")); //$NON-NLS-1$
				comment.append(set.getComment());
				numMatchedSets++;
			}
		}
		return comment.toString();
	}
	
	private boolean containsOne(ChangeSet set, IResource[] resourcesToCommit) {
		for (IResource resource : resourcesToCommit) {
			if (set.contains(resource)) {
				return true;
			}
		}
		return false;
	}

	protected IResource[] promptForResourcesToBeAdded(RepositoryManager manager, IResource[] unadded) {
		return manager.promptForResourcesToBeAdded(getShell(), unadded);
	}
	
	private boolean promptForUnaddedHandling(SyncInfoSet syncSet) throws CVSException {
		if (syncSet.isEmpty()) return false;
		
		// accumulate any resources that are not under version control
		IResource[] unadded = getUnaddedResources(syncSet);
		
		// prompt to get comment and any resources to be added to version control
		RepositoryManager manager = CVSUIPlugin.getPlugin().getRepositoryManager();
		IResource[] toBeAdded = promptForResourcesToBeAdded(manager, unadded);
		if (toBeAdded == null) return false; // User cancelled.
		comment = promptForComment(manager, syncSet.getResources());
		if (comment == null) return false; // User cancelled.
		
		// remove unshared resources that were not selected by the user
		if (unadded != null && unadded.length > 0) {
			List<IResource> resourcesToRemove = new ArrayList<>(unadded.length);
			for (IResource unaddedResource : unadded) {
				boolean included = false;
				for (IResource resourceToAdd : toBeAdded) {
					if (unaddedResource.equals(resourceToAdd)) {
						included = true;
						break;
					}
				}
				if (!included)
					resourcesToRemove.add(unaddedResource);
			}
			syncSet.removeAll(resourcesToRemove.toArray(new IResource[resourcesToRemove.size()]));
		}
		return true;
	}
	
	private IResource[] getUnaddedResources(SyncInfoSet syncSet) throws CVSException {
		// TODO: Should only get outgoing additions (since conflicting additions 
		// could be considered to be under version control already)
		IResource[] resources = syncSet.getResources();
		List<IResource> result = new ArrayList<>();
		for (IResource resource : resources) {
			if (!isAdded(resource)) {
				result.add(resource);
			}
		}
		return result.toArray(new IResource[result.size()]);
	}

	private boolean isAdded(IResource resource) throws CVSException {
		ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
		if (cvsResource.isFolder()) {
			return ((ICVSFolder)cvsResource).isCVSFolder();
		} else {
			return cvsResource.isManaged();
		}
	}

}

Back to the top