Skip to main content
summaryrefslogtreecommitdiffstats
blob: e60495132129052c8c0cc2219e505a6a8b4f4266 (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
359
360
361
362
363
364
365
366
367
368
369
370
371
/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.core;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.subscribers.*;
import org.eclipse.team.core.synchronize.*;
import org.eclipse.team.core.variants.*;
import org.eclipse.team.core.variants.ResourceVariantTreeSubscriber;
import org.eclipse.team.internal.ccvs.core.client.Update;
import org.eclipse.team.internal.ccvs.core.resources.*;
import org.eclipse.team.internal.ccvs.core.syncinfo.*;
import org.eclipse.team.internal.ccvs.core.util.Assert;
import org.eclipse.team.internal.ccvs.core.Policy;

/**
 * CVSSyncInfo
 */
public class CVSSyncInfo extends SyncInfo {

	/*
	 * Codes that are used in returned IStatus
	 */
	private static final int INVALID_RESOURCE_TYPE = 1;
	private static final int INVALID_SYNC_KIND = 2;
	private static final int PARENT_NOT_MANAGED = 3;
	private static final int REMOTE_DOES_NOT_EXIST = 4;
	private static final int SYNC_INFO_CONFLICTS = 5;
	private Subscriber subscriber;

	public CVSSyncInfo(IResource local, IResourceVariant base, IResourceVariant remote, Subscriber subscriber) {
		super(local, base, remote, ((ResourceVariantTreeSubscriber)subscriber).getResourceComparator());
		this.subscriber = subscriber;
	}

	public Subscriber getSubscriber() {
		return subscriber;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.team.core.sync.SyncInfo#computeSyncKind(org.eclipse.core.runtime.IProgressMonitor)
	 */
	protected int calculateKind() throws TeamException {
		// special handling for folders, the generic sync algorithm doesn't work well
		// with CVS because folders are not in namespaces (e.g. they exist in all versions
		// and branches).
		IResource local = getLocal();
		if(local.getType() != IResource.FILE) {
			int folderKind = SyncInfo.IN_SYNC;
			ICVSRemoteFolder remote = (ICVSRemoteFolder)getRemote();
			ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor((IContainer)local);
			boolean isCVSFolder = false;
			try {
				isCVSFolder = cvsFolder.isCVSFolder();
			} catch (CVSException e) {
				// Assume the folder is not a CVS folder
			}
			if(!local.exists()) {
				if(remote != null) {
					if (isCVSFolder) {
						// TODO: This assumes all CVS folders are in-sync even if they have been pruned!
						folderKind = SyncInfo.IN_SYNC;
					} else {
						folderKind = SyncInfo.INCOMING | SyncInfo.ADDITION;
					}
				} else {
					// ignore conflicting deletion to keep phantom sync info
				}
			} else {
				if(remote == null) {
					if(isCVSFolder) {
						// TODO: This is not really an incoming deletion
						// The folder will be pruned once any children are commited
						folderKind = SyncInfo.IN_SYNC;
						//folderKind = SyncInfo.INCOMING | SyncInfo.DELETION;
					} else {
						folderKind = SyncInfo.OUTGOING | SyncInfo.ADDITION;
					}
				} else if(!isCVSFolder) {
					folderKind = SyncInfo.CONFLICTING | SyncInfo.ADDITION;
				} else {
					// folder exists both locally and remotely and are considered in sync, however 
					// we aren't checking the folder mappings to ensure that they are the same.
				}
			}
			return folderKind;
		}
	
		// 1. Run the generic sync calculation algorithm, then handle CVS specific
		// sync cases.
		int kind = super.calculateKind();
	
		// 2. Set the CVS specific sync type based on the workspace sync state provided
		// by the CVS server.
		IResourceVariant remote = getRemote();
		if(remote!=null && (kind & SyncInfo.PSEUDO_CONFLICT) == 0) {
			RemoteResource cvsRemote = (RemoteResource)remote;
			int type = cvsRemote.getWorkspaceSyncState();
			switch(type) {
				// the server compared both text files and decided that it cannot merge
				// them without line conflicts.
				case Update.STATE_CONFLICT: 
					return kind | SyncInfo.MANUAL_CONFLICT;

				// the server compared both text files and decided that it can safely merge
				// them without line conflicts. 
				case Update.STATE_MERGEABLE_CONFLICT: 
					return kind | SyncInfo.AUTOMERGE_CONFLICT;				
			}			
		}
	
		// 3. unmanage delete/delete conflicts and return that they are in sync
		kind = handleDeletionConflicts(kind);
	
		return kind;
	}
	
	/*
	 * If the resource has a delete/delete conflict then ensure that the local is unmanaged so that the 
	 * sync info can be properly flushed.
	 */
	protected int handleDeletionConflicts(int kind) {
		if(kind == (SyncInfo.CONFLICTING | SyncInfo.DELETION | SyncInfo.PSEUDO_CONFLICT)) {
			try {				
				IResource local = getLocal();
				ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(local);
				if(!cvsResource.isFolder() && cvsResource.isManaged()) {
					cvsResource.unmanage(null);
				}
				return SyncInfo.IN_SYNC;
			} catch(CVSException e) {
				CVSProviderPlugin.log(e);
				return SyncInfo.CONFLICTING | SyncInfo.DELETION;
			}
		}
		return kind;
	}

	/*
	 * Update the sync info of the local resource in such a way that the local changes can be committed.
	 * @return IStatus
	 * For folders, the makeInSYnc method is called and the return codes mentioned there apply
	 * for folders.
	 */
	public IStatus makeOutgoing(IProgressMonitor monitor) throws TeamException {
		
		// For folders, there is no outgoing, only in-sync
		if (getLocal().getType() == IResource.FOLDER) {
			return makeInSync();
		}
		int syncKind = getKind();
		boolean incoming = (syncKind & DIRECTION_MASK) == INCOMING;
		boolean outgoing = (syncKind & DIRECTION_MASK) == OUTGOING;

		ICVSResource local = CVSWorkspaceRoot.getCVSResourceFor(getLocal());
		RemoteResource remote = (RemoteResource)getRemote();
		ResourceSyncInfo origInfo = local.getSyncInfo();
		MutableResourceSyncInfo info = null;
		if(origInfo!=null) {
			info = origInfo.cloneMutable();			
		}
	
		if (outgoing) {
				// The sync info is alright, it's already outgoing!
				return Status.OK_STATUS;
		} else if (incoming) {
			// We have an incoming change, addition, or deletion that we want to ignore
			if (local.exists()) {
				// We could have an incoming change or deletion
				if (remote == null) {
					info.setAdded();
				} else {
					// Otherwise change the revision to the remote revision and dirty the file
					info.setRevision(remote.getSyncInfo().getRevision());
					info.setTimeStamp(null);
				}
			} else {
				// We have an incoming add, turn it around as an outgoing delete
				info = remote.getSyncInfo().cloneMutable();
				info.setDeleted(true);
			}
		} else if (local.exists()) {
			// We have a conflict and a local resource!
			if (getRemote() != null) {
				if (getBase() != null) {
					// We have a conflicting change, Update the local revision
					info.setRevision(remote.getSyncInfo().getRevision());
				} else {
					try {
						// We have conflictin additions.
						// We need to fetch the contents of the remote to get all the relevant information (timestamp, permissions)
						// The most important thing we get is the keyword substitution mode which must be right to perform the commit
						remote.getStorage(Policy.monitorFor(monitor)).getContents();
						info = remote.getSyncInfo().cloneMutable();
					} catch (CoreException e) {
						TeamException.asTeamException(e);
					}
				}
			} else if (getBase() != null) {
				// We have a remote deletion. Make the local an addition
				info.setAdded();
			} else {
				// There's a local, no base and no remote. We can't possible have a conflict!
				Assert.isTrue(false);
			} 
		} else {
			// We have a conflict and there is no local!
			if (getRemote() != null) {
				// We have a local deletion that conflicts with remote changes.
				info.setRevision(remote.getSyncInfo().getRevision());
				info.setDeleted(true);
			} else {
				// We have conflicting deletions. Clear the sync info
				info = null;
				return Status.OK_STATUS;
			}
		}
		if(info!=null) {
			FolderSyncInfo parentInfo = local.getParent().getFolderSyncInfo();
			if (parentInfo == null) {
				return new CVSStatus(IStatus.ERROR, PARENT_NOT_MANAGED, Policy.bind("CVSSyncInfo.9", getLocal().getFullPath().toString())); //$NON-NLS-1$
			}
			info.setTag(parentInfo.getTag());
		}
		((ICVSFile)local).setSyncInfo(info, ICVSFile.UNKNOWN);
		return Status.OK_STATUS;
	}
	
	/*
	 * Update the sync info of the local resource in such a way that the remote resource can be loaded 
	 * ignore any local changes. 
	 */
	public void makeIncoming(IProgressMonitor monitor) throws TeamException {
		// To make outgoing deletions incoming, the local will not exist but
		// it is still important to unmanage (e.g. delete all meta info) for the
		// deletion.
		CVSWorkspaceRoot.getCVSResourceFor(getLocal()).unmanage(monitor);
	}
	
	/*
	 * Load the resource and folder sync info into the local from the remote
	 * 
	 * This method can be used on incoming folder additions to set the folder sync info properly
	 * without hitting the server again. It also applies to conflicts that involves unmanaged
	 * local resources.
	 * 
	 * @return an IStatus with the following severity and codes
	 * <ul>
	 * <li>IStatus.WARNING
	 * 	<ul>
	 *   <li>INVALID_RESOURCE_TYPE - makeInSync only works on folders
	 *   <li>INVALID_SYNC_KIND - sync direction must be incoming or conflicting
	 *  </ul>
	 * <li>IStatus.ERROR
	 *  <ul>
	 *   <li>PARENT_NOT_MANAGED - the local parent of the resource is not under CVS control
	 *   <li>SYNC_INFO_CONFLICTS - Sync info already exists locally and differs from the info
	 *     in the remote handle.
	 *   <li>REMOTE_DOES_NOT_EXIST - There is no local sync info and there is no remote handle
	 *  </ul>
	 * </ul>
	 */
	 public IStatus makeInSync() throws CVSException {
	 	
	 	// Only works on folders
		if (getLocal().getType() == IResource.FILE) {
			return new CVSStatus(IStatus.WARNING, INVALID_RESOURCE_TYPE, Policy.bind("CVSSyncInfo.7", getLocal().getFullPath().toString())); //$NON-NLS-1$
		}
	 	
		// Only works on outgoing and conflicting changes
		boolean outgoing = (getKind() & DIRECTION_MASK) == OUTGOING;
		if (outgoing) {
			return new CVSStatus(IStatus.WARNING, INVALID_SYNC_KIND, Policy.bind("CVSSyncInfo.8", getLocal().getFullPath().toString())); //$NON-NLS-1$
		}
		
		// The parent must be managed
		ICVSFolder local = CVSWorkspaceRoot.getCVSFolderFor((IContainer)getLocal());
		if (getLocal().getType() == IResource.FOLDER && ! local.getParent().isCVSFolder())
			return new CVSStatus(IStatus.ERROR, PARENT_NOT_MANAGED, Policy.bind("CVSSyncInfo.9", getLocal().getFullPath().toString())); //$NON-NLS-1$
		
		// Ensure that the folder exists locally
		if (! local.exists()) {
			local.mkdir();
		}
		
		// If the folder already has CVS info, check that the remote and local match
		RemoteFolder remote = (RemoteFolder)getRemote();
		if((local.isManaged() || getLocal().getType() == IResource.PROJECT) && local.isCVSFolder()) {
			// If there's no remote, assume everything is OK
			if (remote == null) return Status.OK_STATUS;
			// Verify that the root and repository are the same
			FolderSyncInfo remoteInfo = remote.getFolderSyncInfo();
			FolderSyncInfo localInfo = local.getFolderSyncInfo();
			if ( ! localInfo.getRoot().equals(remoteInfo.getRoot())) {
				return new CVSStatus(IStatus.ERROR, SYNC_INFO_CONFLICTS, Policy.bind("CVSRemoteSyncElement.rootDiffers", new Object[] {local.getName(), remoteInfo.getRoot(), localInfo.getRoot()}));//$NON-NLS-1$
			} else if ( ! localInfo.getRepository().equals(remoteInfo.getRepository())) {
				return new CVSStatus(IStatus.ERROR, SYNC_INFO_CONFLICTS, Policy.bind("CVSRemoteSyncElement.repositoryDiffers", new Object[] {local.getName(), remoteInfo.getRepository(), localInfo.getRepository()}));//$NON-NLS-1$
			}
			// The folders are in sync so just return
			return Status.OK_STATUS;
		}
		
		// The remote must exist if the local is not managed
		if (remote == null) {
			return new CVSStatus(IStatus.ERROR, REMOTE_DOES_NOT_EXIST, Policy.bind("CVSSyncInfo.10", getLocal().getFullPath().toString())); //$NON-NLS-1$
		}
		
		// Since the parent is managed, this will also set the resource sync info. It is
		// impossible for an incoming folder addition to map to another location in the
		// repo, so we assume that using the parent's folder sync as a basis is safe.
		// It is also impossible for an incomming folder to be static.
		FolderSyncInfo remoteInfo = remote.getFolderSyncInfo();
		FolderSyncInfo localInfo = local.getParent().getFolderSyncInfo();
		local.setFolderSyncInfo(new FolderSyncInfo(remoteInfo.getRepository(), remoteInfo.getRoot(), localInfo.getTag(), false));
		return Status.OK_STATUS;
	}
	
	public String toString() {
		IResourceVariant base = getBase();
		IResourceVariant remote = getRemote();
		StringBuffer result = new StringBuffer(super.toString());
		result.append("Local: "); //$NON-NLS-1$
		result.append(getLocal().toString());
		result.append(" Base: "); //$NON-NLS-1$
		if (base == null) {
			result.append("none"); //$NON-NLS-1$
		} else {
			result.append(base.toString());
		}
		result.append(" Remote: "); //$NON-NLS-1$
		if (remote == null) {
			result.append("none"); //$NON-NLS-1$
		} else {
			result.append(remote.toString());
		}
		return result.toString();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.team.core.subscribers.SyncInfo#getContentIdentifier()
	 */
	public String getLocalContentIdentifier() {
		try {
			IResource local = getLocal();
			if (local != null || local.getType() == IResource.FILE) {
				// it's a file, return the revision number if we can find one
				ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor((IFile) local);
				ResourceSyncInfo info = cvsFile.getSyncInfo();
				if (info != null) {
					return info.getRevision();
				}
			}
		} catch (CVSException e) {
			CVSProviderPlugin.log(e);
			return null;
		}
		return null;
	}
}

Back to the top