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