Skip to main content
summaryrefslogtreecommitdiffstats
blob: 7c038c972016fe3806fba9b1c12aa76c729e8728 (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
/*******************************************************************************
 * 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.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.subscribers.SyncInfo;
import org.eclipse.team.core.subscribers.TeamSubscriber;
import org.eclipse.team.core.sync.IRemoteResource;
import org.eclipse.team.internal.ccvs.core.client.Update;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.RemoteFolder;
import org.eclipse.team.internal.ccvs.core.resources.RemoteResource;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.MutableResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.Assert;

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

	public CVSSyncInfo(IResource local, IRemoteResource base, IRemoteResource remote, TeamSubscriber subscriber, IProgressMonitor monitor) throws TeamException {
		super(local, base, remote, subscriber, monitor);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.team.core.sync.SyncInfo#computeSyncKind(org.eclipse.core.runtime.IProgressMonitor)
	 */
	protected int calculateKind(IProgressMonitor progress) 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 && getSubscriber().isThreeWay()) {
			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(progress);
	
		// 2. Set the CVS specific sync type based on the workspace sync state provided
		// by the CVS server.
		IRemoteResource 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;
	}

	/**
	 * Return true if the provided phantom folder conyains any outgoing file deletions.
	 * We only need to detect if there are any files since a phantom folder can only
	 * contain outgoing filre deletions and other folder.
	 * 
	 * @param cvsFolder a phantom folder
	 * @return boolean
	 */
	private boolean containsOutgoingDeletions(ICVSFolder cvsFolder) {
		final boolean result[] = new boolean[] { false };
		try {
			cvsFolder.accept(new ICVSResourceVisitor() {
				public void visitFile(ICVSFile file) throws CVSException {
					// Do nothing. Files are handled below
				}
				public void visitFolder(ICVSFolder folder) throws CVSException {
					if (folder.members(ICVSFolder.FILE_MEMBERS).length > 0) {
						result[0] = true;
					} else {
						folder.acceptChildren(this);
					}
				}
			});
		} catch (CVSException e) {
			CVSProviderPlugin.log(e);
		}
		return result[0];
	}
	
	/*
	 * 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.
	 */
	public void makeOutgoing(IProgressMonitor monitor) throws TeamException {
		
		// TODO: What is the impact of using whatever the current granularity is?
		// int syncKind = getSyncKind(GRANULARITY_TIMESTAMP , monitor);
		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;
		} 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 {
					// We have conflictin additions.
					// We need to fetch the contents of the remote to get all the relevant information (timestamp, permissions)
					// TODO: Do we really need to fetch the contents here?
					remote.getContents(Policy.monitorFor(monitor));
					info = remote.getSyncInfo().cloneMutable();
				}
			} 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;
			}
		}
		if(info!=null) {
			info.setTag(local.getParent().getFolderSyncInfo().getTag());
		}
		((ICVSFile)local).setSyncInfo(info, ICVSFile.UNKNOWN);
	}
	
	/*
	 * 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(null);
	}
	
	/*
	 * 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.
	 * 
	 * If the local folder is already managed and is a cvs folder, this operation
	 * will throw an exception if the mapping does not match that of the remote.
	 */
	 public void makeInSync() throws CVSException {
	 	
		// Only work on folders
		if (getLocal().getType() == IResource.FILE) return;
	 		
		boolean outgoing = (getKind() & DIRECTION_MASK) == OUTGOING;
		if (outgoing) return;
		
		ICVSFolder local = (ICVSFolder)CVSWorkspaceRoot.getCVSFolderFor((IContainer)getLocal());
		RemoteFolder remote = (RemoteFolder)getRemote();
		
		// The parent must be managed
		if (! local.getParent().isCVSFolder())
			return;
		
		// 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
		if(local.isManaged() && local.isCVSFolder()) {
			// Verify that the root and repository are the same
			FolderSyncInfo remoteInfo = remote.getFolderSyncInfo();
			FolderSyncInfo localInfo = local.getFolderSyncInfo();
			if ( ! localInfo.getRoot().equals(remoteInfo.getRoot())) {
				throw new CVSException(Policy.bind("CVSRemoteSyncElement.rootDiffers", new Object[] {local.getName(), remoteInfo.getRoot(), localInfo.getRoot()}));//$NON-NLS-1$
			} else if ( ! localInfo.getRepository().equals(remoteInfo.getRepository())) {
				throw new CVSException(Policy.bind("CVSRemoteSyncElement.repositoryDiffers", new Object[] {local.getName(), remoteInfo.getRepository(), localInfo.getRepository()}));//$NON-NLS-1$
			}
			// The folders are in sync so just return
			return;
		}
		
		// 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));
	}
	
	public String toString() {
		IResource local = getLocal();
		IRemoteResource base = getBase();
		IRemoteResource 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