1 /*******************************************************************************
2  * Copyright (c) 2000, 2005 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.team.internal.ccvs.ui.operations;
15 
16 import java.util.*;
17 
18 import org.eclipse.core.resources.ResourcesPlugin;
19 import org.eclipse.core.runtime.IProgressMonitor;
20 import org.eclipse.core.runtime.IStatus;
21 import org.eclipse.osgi.util.NLS;
22 import org.eclipse.team.core.TeamException;
23 import org.eclipse.team.internal.ccvs.core.*;
24 import org.eclipse.team.internal.ccvs.core.client.*;
25 import org.eclipse.team.internal.ccvs.core.client.listeners.ILogEntryListener;
26 import org.eclipse.team.internal.ccvs.core.client.listeners.LogListener;
27 import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
28 import org.eclipse.team.internal.ccvs.core.util.Util;
29 import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
30 import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
31 import org.eclipse.team.internal.ccvs.ui.Policy;
32 import org.eclipse.ui.IWorkbenchPart;
33 
34 /**
35  * Performs an rlog on the resources and caches the results.
36  */
37 public class RemoteLogOperation extends RepositoryLocationOperation {
38 
39 	private RLog rlog = new RLog();
40 	private CVSTag tag1;
41 	private CVSTag tag2;
42 	private LogEntryCache entryCache;
43 
44 	/**
45 	 * A log entry cache that can be shared by multiple instances of the
46 	 * remote log operation.
47 	 */
48 	public static class LogEntryCache implements ILogEntryListener {
49 
50 		/*
51 		 * Cache of all log entries
52 		 */
53 		private Map<String, Map<String, ILogEntry>> entries = new HashMap<>(); /*
54 																				 * Map String:remoteFilePath->Map
55 																				 * (String:revision -> ILogEntry)
56 																				 */
57 
internalGetLogEntries(String path)58 		private Map<String, ILogEntry> internalGetLogEntries(String path) {
59 			return entries.get(path);
60 		}
61 
62 		/**
63 		 * Return all the log entries at the given path
64 		 * @param path the file path
65 		 * @return the log entries for the file
66 		 */
getLogEntries(String path)67 		public ILogEntry[] getLogEntries(String path) {
68 			Map<String, ILogEntry> map = internalGetLogEntries(path);
69 			return map.values().toArray(new ILogEntry[map.size()]);
70 		}
71 
internalGetLogEntry(String path, String revision)72 		private ILogEntry internalGetLogEntry(String path, String revision) {
73 			Map fileEntries = internalGetLogEntries(path);
74 			if (fileEntries != null) {
75 				return (ILogEntry)fileEntries.get(revision);
76 			}
77 			return null;
78 		}
79 
getCachedFilePaths()80 		public String[] getCachedFilePaths() {
81 			return entries.keySet().toArray(new String[entries.size()]);
82 		}
83 
84 		/**
85 		 * Return the log entry that for the given resource
86 		 * or <code>null</code> if no entry was fetched or the
87 		 * resource is not a file.
88 		 * @param getFullPath(resource) the resource
89 		 * @return the log entry or <code>null</code>
90 		 */
getLogEntry(ICVSRemoteResource resource)91 		public synchronized ILogEntry getLogEntry(ICVSRemoteResource resource) {
92 			if (resource instanceof ICVSRemoteFile) {
93 				try {
94 					String path = getFullPath(resource);
95 					String revision = ((ICVSRemoteFile)resource).getRevision();
96 					return internalGetLogEntry(path, revision);
97 				} catch (TeamException e) {
98 					// Log and return null
99 					CVSUIPlugin.log(e);
100 				}
101 			}
102 			return null;
103 		}
104 
105 		/**
106 		 * Return the log entries that were fetched for the given resource
107 		 * or an empty list if no entry was fetched.
108 		 * @param getFullPath(resource) the resource
109 		 * @return the fetched log entries or an empty list is none were found
110 		 */
getLogEntries(ICVSRemoteResource resource)111 		public synchronized ILogEntry[] getLogEntries(ICVSRemoteResource resource) {
112 			Map<String, ILogEntry> fileEntries = internalGetLogEntries(getFullPath(resource));
113 			if (fileEntries != null) {
114 				return fileEntries.values().toArray(new ILogEntry[fileEntries.size()]);
115 			}
116 			return new ILogEntry[0];
117 		}
118 
119 		/*
120 		 * Return the full path that uniquely identifies the resource
121 		 * accross repositories. This path include the repository and
122 		 * resource path but does not include the revision so that
123 		 * all log entries for a file can be retrieved.
124 		 */
getFullPath(ICVSRemoteResource resource)125 		private String getFullPath(ICVSRemoteResource resource) {
126 			return Util.appendPath(resource.getRepository().getLocation(false), resource.getRepositoryRelativePath());
127 		}
128 
clearEntries()129 		public synchronized void clearEntries() {
130 			entries.clear();
131 		}
132 
getImmediatePredecessor(ICVSRemoteFile file)133 		public synchronized ICVSRemoteFile getImmediatePredecessor(ICVSRemoteFile file) throws TeamException {
134 			ILogEntry[] allLogs = getLogEntries(file);
135 			String revision = file.getRevision();
136 			// First decrement the last digit and see if that revision exists
137 			String predecessorRevision = getPredecessorRevision(revision);
138 			ICVSRemoteFile predecessor = findRevison(allLogs, predecessorRevision);
139 			// If nothing was found, try to fond the base of a branch
140 			if (predecessor == null && isBrancheRevision(revision)) {
141 				predecessorRevision = getBaseRevision(revision);
142 				predecessor = findRevison(allLogs, predecessorRevision);
143 			}
144 			// If that fails, it is still possible that there is a revision.
145 			// This can happen if the revision has been manually set.
146 			if (predecessor == null) {
147 				// We don't search in this case since this is costly and would be done
148 				// for any file that is new as well.
149 			}
150 			return predecessor;
151 		}
152 
153 		/*
154 		 * Find the given revision in the list of log entries.
155 		 * Return null if the revision wasn't found.
156 		 */
findRevison(ILogEntry[] allLogs, String predecessorRevision)157 		private ICVSRemoteFile findRevison(ILogEntry[] allLogs, String predecessorRevision) throws TeamException {
158 			for (ILogEntry entry : allLogs) {
159 				ICVSRemoteFile file = entry.getRemoteFile();
160 				if (file.getRevision().equals(predecessorRevision)) {
161 					return file;
162 				}
163 			}
164 			return null;
165 		}
166 		/*
167 		 * Decrement the trailing digit by one.
168 		 */
getPredecessorRevision(String revision)169 		private String getPredecessorRevision(String revision) {
170 			int digits[] = Util.convertToDigits(revision);
171 			digits[digits.length -1]--;
172 			StringBuilder buffer = new StringBuilder(revision.length());
173 			for (int i = 0; i < digits.length; i++) {
174 				buffer.append(Integer.toString(digits[i]));
175 				if (i < digits.length - 1) {
176 					buffer.append('.');
177 				}
178 			}
179 			return buffer.toString();
180 		}
181 
182 		/*
183 		 * Return true if there are more than 2 digits in the revision number
184 		 * (i.e. the revision is on a branch)
185 		 */
isBrancheRevision(String revision)186 		private boolean isBrancheRevision(String revision) {
187 			return Util.convertToDigits(revision).length > 2;
188 		}
189 
190 		/*
191 		 * Remove the trailing revision digits such that the
192 		 * returned revision is shorter than the given revision
193 		 * and is an even number of digits long
194 		 */
getBaseRevision(String revision)195 		private String getBaseRevision(String revision) {
196 			int digits[] = Util.convertToDigits(revision);
197 			int length = digits.length - 1;
198 			if (length % 2 == 1) {
199 				length--;
200 			}
201 			StringBuilder buffer = new StringBuilder(revision.length());
202 			for (int i = 0; i < length; i++) {
203 				buffer.append(Integer.toString(digits[i]));
204 				if (i < length - 1) {
205 					buffer.append('.');
206 				}
207 			}
208 			return buffer.toString();
209 		}
210 		/**
211 		 * Remove any entries for the remote resources
212 		 * @param resource the remote resource
213 		 */
clearEntries(ICVSRemoteResource resource)214 		public synchronized void clearEntries(ICVSRemoteResource resource) {
215 			String remotePath = getFullPath(resource);
216 			entries.remove(remotePath);
217 		}
218 
219 		@Override
handleLogEntryReceived(ILogEntry entry)220 		public void handleLogEntryReceived(ILogEntry entry) {
221 			ICVSRemoteFile file = entry.getRemoteFile();
222 			String fullPath = getFullPath(file);
223 			String revision = entry.getRevision();
224 			Map<String, ILogEntry> fileEntries = internalGetLogEntries(fullPath);
225 			if (fileEntries == null) {
226 				fileEntries = new HashMap<>();
227 				entries.put(fullPath, fileEntries);
228 			}
229 			fileEntries.put(revision, entry);
230 		}
231 	}
232 
RemoteLogOperation(IWorkbenchPart part, ICVSRemoteResource[] remoteResources, CVSTag tag1, CVSTag tag2, LogEntryCache cache)233 	public RemoteLogOperation(IWorkbenchPart part, ICVSRemoteResource[] remoteResources, CVSTag tag1, CVSTag tag2, LogEntryCache cache) {
234 		super(part, remoteResources);
235 		this.tag1 = tag1;
236 		this.tag2 = tag2;
237 		this.entryCache = cache;
238 	}
239 
240 	@Override
execute(ICVSRepositoryLocation location, ICVSRemoteResource[] remoteResources, IProgressMonitor monitor)241 	protected void execute(ICVSRepositoryLocation location, ICVSRemoteResource[] remoteResources, IProgressMonitor monitor) throws CVSException {
242 		monitor.beginTask(NLS.bind(CVSUIMessages.RemoteLogOperation_0, new String[] { location.getHost() }), 100);
243 		Session s = new Session(location, CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()), false /* do not output to console */);
244 		// Create a log listener that will update the cache as entries are received
245 		LogListener listener = new LogListener(entryCache);
246 
247 		ICVSRemoteResource[] remotes = remoteResources;
248 		Command.LocalOption[] localOptions = getLocalOptions(tag1, tag2);
249 		if(tag1 == null || tag2 == null) {
250 			// Optimize the cases were we are only fetching the history for a single revision. If it is
251 			// already cached, don't fetch it again.
252 			ArrayList<ICVSRemoteResource> unCachedRemotes = new ArrayList<>();
253 			for (ICVSRemoteResource r : remoteResources) {
254 				if(entryCache.getLogEntry(r) == null) {
255 					unCachedRemotes.add(r);
256 				}
257 			}
258 			remotes = unCachedRemotes.toArray(new ICVSRemoteResource[unCachedRemotes.size()]);
259 		}
260 		if (remotes.length > 0) {
261 			try {
262 				s.open(Policy.subMonitorFor(monitor, 10));
263 				IStatus status = rlog.execute(s, Command.NO_GLOBAL_OPTIONS, localOptions, remotes, listener, Policy.subMonitorFor(monitor, 90));
264 				collectStatus(status);
265 			} finally {
266 				s.close();
267 			}
268 		}
269 	}
270 
271 	@Override
getTaskName()272 	protected String getTaskName() {
273 		return CVSUIMessages.RemoteLogOperation_1;
274 	}
275 
getLocalOptions(CVSTag tag1, CVSTag tag2)276 	protected Command.LocalOption[] getLocalOptions(CVSTag tag1, CVSTag tag2) {
277 		if(tag1 != null && tag2 != null) {
278 			return new Command.LocalOption[] {RLog.NO_TAGS, RLog.ONLY_INCLUDE_CHANGES, RLog.makeTagOption(tag1, tag2)};
279 		}
280 		else if (tag1 != null){
281 			if (tag1.getType() == CVSTag.HEAD ||
282 				tag1.getType() == CVSTag.VERSION)
283 				return new Command.LocalOption[] {RLog.NO_TAGS, RLog.ONLY_INCLUDE_CHANGES, RLog.getCurrentTag(tag1)};
284 
285 			if (tag1.getType() == CVSTag.DATE)
286 				return new Command.LocalOption[] {RLog.NO_TAGS, RLog.ONLY_INCLUDE_CHANGES, RLog.REVISIONS_ON_DEFAULT_BRANCH, RLog.getCurrentTag(tag1)};
287 			//branch tag
288 			return new Command.LocalOption[] {RLog.getCurrentTag(tag1)};
289 		}
290 		else {
291 			return new Command.LocalOption[] {RLog.NO_TAGS, RLog.ONLY_INCLUDE_CHANGES};
292 		}
293 	}
294 }
295