| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.cvsSupport2.changeBrowser; |
| |
| import com.intellij.CvsBundle; |
| import com.intellij.cvsSupport2.CvsUtil; |
| import com.intellij.cvsSupport2.application.CvsEntriesManager; |
| import com.intellij.cvsSupport2.connections.CvsEnvironment; |
| import com.intellij.cvsSupport2.history.CvsRevisionNumber; |
| import com.intellij.openapi.cvsIntegration.CvsResult; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.Trinity; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.actions.VcsContextFactory; |
| import com.intellij.openapi.vcs.changes.ChangesUtil; |
| import com.intellij.openapi.vcs.changes.committed.*; |
| import com.intellij.openapi.vcs.history.VcsRevisionNumber; |
| import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; |
| import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor; |
| import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.AsynchConsumer; |
| import com.intellij.util.Consumer; |
| import gnu.trove.TObjectLongHashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.netbeans.lib.cvsclient.admin.Entry; |
| import org.netbeans.lib.cvsclient.command.log.Revision; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| |
| /** |
| * @author yole |
| */ |
| public class CvsCommittedChangesProvider implements CachingCommittedChangesProvider<CvsChangeList, ChangeBrowserSettings> { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.cvsSupport2.changeBrowser.CvsCommittedChangesProvider"); |
| |
| private final Project myProject; |
| |
| public CvsCommittedChangesProvider(Project project) { |
| myProject = project; |
| } |
| |
| @NotNull |
| public ChangeBrowserSettings createDefaultSettings() { |
| return new ChangeBrowserSettings(); |
| } |
| |
| public ChangesBrowserSettingsEditor<ChangeBrowserSettings> createFilterUI(final boolean showDateFilter) { |
| return new CvsVersionFilterComponent(showDateFilter); |
| } |
| |
| @Nullable |
| public VcsCommittedListsZipper getZipper() { |
| return new MyZipper(); |
| } |
| |
| private static class MyZipper extends VcsCommittedListsZipperAdapter { |
| private long lastNumber = 1; |
| private final TObjectLongHashMap<CommittedChangeListKey> numberCache = new TObjectLongHashMap<CommittedChangeListKey>(); |
| |
| private MyZipper() { |
| super(new GroupCreator() { |
| public Object createKey(final RepositoryLocation location) { |
| final CvsRepositoryLocation cvsLocation = (CvsRepositoryLocation) location; |
| return cvsLocation.getEnvironment().getRepository(); |
| } |
| |
| public RepositoryLocationGroup createGroup(final Object key, final Collection<RepositoryLocation> locations) { |
| final RepositoryLocationGroup group = new RepositoryLocationGroup((String) key); |
| for (RepositoryLocation location : locations) { |
| group.add(location); |
| } |
| return group; |
| } |
| }); |
| } |
| |
| @Override |
| public long getNumber(final CommittedChangeList list) { |
| final long time = list.getCommitDate().getTime(); |
| final Long roundedTime = Long.valueOf(time - (time % CvsChangeList.SUITABLE_DIFF)); |
| final CommittedChangeListKey key = new CommittedChangeListKey(list.getCommitterName(), roundedTime, list.getComment()); |
| final long number = numberCache.get(key); |
| if (number == 0) { |
| numberCache.put(key, lastNumber); |
| return lastNumber++; |
| } |
| return number; |
| } |
| } |
| |
| private static class CommittedChangeListKey extends Trinity<String, Long, String> { |
| |
| CommittedChangeListKey(String name, Long commitDate, String comment) { |
| super(name, commitDate, comment); |
| } |
| } |
| |
| @Nullable |
| public CvsRepositoryLocation getLocationFor(final FilePath root) { |
| if (!CvsUtil.fileIsUnderCvs(root.getIOFile())) { |
| return null; |
| } |
| final VirtualFile rootDir = root.isDirectory() ? root.getVirtualFile() : root.getVirtualFileParent(); |
| final String module = CvsUtil.getModuleName(root); |
| final CvsEnvironment connectionSettings = CvsEntriesManager.getInstance().getCvsConnectionSettingsFor(rootDir); |
| return new CvsRepositoryLocation(root.getVirtualFile(), connectionSettings, module); |
| } |
| |
| public RepositoryLocation getLocationFor(final FilePath root, final String repositoryPath) { |
| return getLocationFor(root); |
| } |
| |
| public ChangeListColumn[] getColumns() { |
| return new ChangeListColumn[] { ChangeListColumn.DATE, ChangeListColumn.NAME, ChangeListColumn.DESCRIPTION, BRANCH_COLUMN }; |
| } |
| |
| @Nullable |
| public VcsCommittedViewAuxiliary createActions(final DecoratorManager manager, final RepositoryLocation location) { |
| return null; |
| } |
| |
| public int getUnlimitedCountValue() { |
| return 0; |
| } |
| |
| @Nullable |
| @Override |
| public Pair<CvsChangeList, FilePath> getOneList(VirtualFile file, final VcsRevisionNumber number) throws VcsException { |
| final File ioFile = new File(file.getPath()); |
| final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(ioFile); |
| final VirtualFile vcsRoot = ProjectLevelVcsManager.getInstance(myProject).getVcsRootFor(filePath); |
| final CvsRepositoryLocation cvsLocation = getLocationFor(filePath); |
| if (cvsLocation == null) return null; |
| final String module = CvsUtil.getModuleName(vcsRoot); |
| final CvsEnvironment connectionSettings = cvsLocation.getEnvironment(); |
| if (connectionSettings.isOffline()) { |
| return null; |
| } |
| final CvsChangeListsBuilder builder = new CvsChangeListsBuilder(module, connectionSettings, myProject, vcsRoot); |
| |
| final Ref<CvsChangeList> result = new Ref<CvsChangeList>(); |
| final LoadHistoryOperation operation = new LoadHistoryOperation(connectionSettings, new Consumer<LogInformationWrapper>() { |
| public void consume(LogInformationWrapper wrapper) { |
| final List<Revision> revisions = wrapper.getRevisions(); |
| if (revisions.isEmpty()) return; |
| final RevisionWrapper revision = new RevisionWrapper(wrapper.getFile(), revisions.get(0), null); |
| result.set(builder.addRevision(revision)); |
| } |
| }, cvsLocation.getModuleName(), number.asString()); |
| final CvsResult executionResult = operation.run(myProject); |
| |
| if (executionResult.isCanceled()) { |
| throw new ProcessCanceledException(); |
| } |
| else if (executionResult.hasErrors()) { |
| throw executionResult.composeError(); |
| } |
| if (result.isNull()) { |
| return null; |
| } |
| final Date commitDate = result.get().getCommitDate(); |
| final CvsEnvironment rootConnectionSettings = CvsEntriesManager.getInstance().getCvsConnectionSettingsFor(vcsRoot); |
| final long t = commitDate.getTime(); |
| final Date dateFrom = new Date(t - CvsChangeList.SUITABLE_DIFF); |
| final Date dateTo = new Date(t + CvsChangeList.SUITABLE_DIFF); |
| |
| final LoadHistoryOperation operation2 = |
| new LoadHistoryOperation(rootConnectionSettings, module, dateFrom, dateTo, new Consumer<LogInformationWrapper>() { |
| @Override |
| public void consume(LogInformationWrapper wrapper) { |
| final List<RevisionWrapper> wrappers = builder.revisionWrappersFromLog(wrapper); |
| if (wrappers != null) { |
| for (RevisionWrapper revisionWrapper : wrappers) { |
| if (result.get().containsFileRevision(revisionWrapper)) { |
| // otherwise a new change list will be created because the old change list already contains this file. |
| continue; |
| } |
| builder.addRevision(revisionWrapper); |
| } |
| } |
| } |
| }); |
| final CvsResult cvsResult = operation2.run(myProject); |
| if (cvsResult.hasErrors()) { |
| throw cvsResult.composeError(); |
| } |
| return Pair.create(result.get(), filePath); |
| } |
| |
| @Override |
| public RepositoryLocation getForNonLocal(VirtualFile file) { |
| return null; |
| } |
| |
| @Override |
| public boolean supportsIncomingChanges() { |
| return true; |
| } |
| |
| public List<CvsChangeList> getCommittedChanges(ChangeBrowserSettings settings, RepositoryLocation location, final int maxCount) |
| throws VcsException { |
| final CvsRepositoryLocation cvsLocation = (CvsRepositoryLocation) location; |
| final String module = cvsLocation.getModuleName(); |
| final VirtualFile rootFile = cvsLocation.getRootFile(); |
| return loadCommittedChanges(settings, module, cvsLocation.getEnvironment(), rootFile); |
| } |
| |
| public void loadCommittedChanges(ChangeBrowserSettings settings, |
| RepositoryLocation location, |
| int maxCount, |
| final AsynchConsumer<CommittedChangeList> consumer) |
| throws VcsException { |
| try { |
| final CvsRepositoryLocation cvsLocation = (CvsRepositoryLocation) location; |
| final String module = cvsLocation.getModuleName(); |
| final CvsEnvironment connectionSettings = cvsLocation.getEnvironment(); |
| if (connectionSettings.isOffline()) { |
| return; |
| } |
| final CvsChangeListsBuilder builder = new CvsChangeListsBuilder(module, connectionSettings, myProject, cvsLocation.getRootFile()); |
| final Date dateTo = settings.getDateBeforeFilter(); |
| Date dateFrom = settings.getDateAfterFilter(); |
| if (dateFrom == null) { |
| final Calendar calendar = Calendar.getInstance(); |
| calendar.set(1970, Calendar.MARCH, 2); |
| dateFrom = calendar.getTime(); |
| } |
| final ChangeBrowserSettings.Filter filter = settings.createFilter(); |
| final Set<CvsChangeList> controlSet = new HashSet<CvsChangeList>(); |
| final LoadHistoryOperation operation = |
| new LoadHistoryOperation(connectionSettings, module, dateFrom, dateTo, new Consumer<LogInformationWrapper>() { |
| public void consume(LogInformationWrapper wrapper) { |
| final List<RevisionWrapper> wrappers = builder.revisionWrappersFromLog(wrapper); |
| if (wrappers != null) { |
| for (RevisionWrapper revisionWrapper : wrappers) { |
| final CvsChangeList changeList = builder.addRevision(revisionWrapper); |
| if (controlSet.contains(changeList)) continue; |
| controlSet.add(changeList); |
| if (filter.accepts(changeList)) { |
| consumer.consume(changeList); |
| } |
| } |
| } |
| } |
| }); |
| final CvsResult executionResult = operation.run(myProject); |
| |
| if (executionResult.isCanceled()) { |
| throw new ProcessCanceledException(); |
| } |
| else if (executionResult.hasErrors()) { |
| throw executionResult.composeError(); |
| } |
| } |
| finally { |
| consumer.finished(); |
| } |
| } |
| |
| private List<CvsChangeList> loadCommittedChanges(final ChangeBrowserSettings settings, |
| final String module, |
| CvsEnvironment connectionSettings, |
| final VirtualFile rootFile) throws VcsException { |
| if (connectionSettings.isOffline()) { |
| return Collections.emptyList(); |
| } |
| final CvsChangeListsBuilder builder = new CvsChangeListsBuilder(module, connectionSettings, myProject, rootFile); |
| final Date dateTo = settings.getDateBeforeFilter(); |
| Date dateFrom = settings.getDateAfterFilter(); |
| if (dateFrom == null) { |
| final Calendar calendar = Calendar.getInstance(); |
| calendar.set(1970, Calendar.MARCH, 2); |
| dateFrom = calendar.getTime(); |
| } |
| final LoadHistoryOperation operation = |
| new LoadHistoryOperation(connectionSettings, module, dateFrom, dateTo, new Consumer<LogInformationWrapper>() { |
| @Override |
| public void consume(LogInformationWrapper logInformationWrapper) { |
| builder.add(logInformationWrapper); |
| } |
| }); |
| final CvsResult executionResult = operation.run(myProject); |
| |
| if (executionResult.isCanceled()) { |
| throw new ProcessCanceledException(); |
| } |
| else if (executionResult.hasErrors()) { |
| throw executionResult.composeError(); |
| } |
| else { |
| final List<CvsChangeList> versions = builder.getVersions(); |
| settings.filterChanges(versions); |
| return versions; |
| } |
| } |
| |
| public int getFormatVersion() { |
| return 3; |
| } |
| |
| public void writeChangeList(final DataOutput stream, final CvsChangeList list) throws IOException { |
| list.writeToStream(stream); |
| } |
| |
| public CvsChangeList readChangeList(final RepositoryLocation location, final DataInput stream) throws IOException { |
| final CvsRepositoryLocation cvsLocation = (CvsRepositoryLocation) location; |
| return new CvsChangeList(myProject, cvsLocation.getEnvironment(), cvsLocation.getRootFile(), stream); |
| } |
| |
| public boolean isMaxCountSupported() { |
| return false; |
| } |
| |
| public Collection<FilePath> getIncomingFiles(final RepositoryLocation location) { |
| return null; |
| } |
| |
| public boolean refreshCacheByNumber() { |
| return false; |
| } |
| |
| public String getChangelistTitle() { |
| return null; |
| } |
| |
| public boolean isChangeLocallyAvailable(final FilePath filePath, @Nullable VcsRevisionNumber localRevision, |
| VcsRevisionNumber changeRevision, final CvsChangeList changeList) { |
| if (localRevision instanceof CvsRevisionNumber && changeRevision instanceof CvsRevisionNumber) { |
| final CvsRevisionNumber cvsLocalRevision = (CvsRevisionNumber)localRevision; |
| final CvsRevisionNumber cvsChangeRevision = (CvsRevisionNumber)changeRevision; |
| final int[] localSubRevisions = cvsLocalRevision.getSubRevisions(); |
| final int[] changeSubRevisions = cvsChangeRevision.getSubRevisions(); |
| if (localSubRevisions != null && changeSubRevisions != null) { |
| if (localSubRevisions.length != changeSubRevisions.length) { |
| // local is trunk, change is branch / vice versa |
| return true; |
| } |
| for(int i=2; i<localSubRevisions.length; i += 2) { |
| if (localSubRevisions [i] != changeSubRevisions [i]) { |
| // local is one branch, change is a different branch |
| return true; |
| } |
| } |
| } |
| } |
| |
| return isDifferentBranch(filePath, changeList) || (localRevision != null && localRevision.compareTo(changeRevision) >= 0); |
| } |
| |
| private static boolean isDifferentBranch(final FilePath filePath, final CvsChangeList changeList) { |
| final String localTag; |
| final CvsEntriesManager cvsEntriesManager = CvsEntriesManager.getInstance(); |
| final VirtualFile parent = filePath.getVirtualFileParent(); |
| if (parent != null) { |
| final Entry entry = cvsEntriesManager.getEntryFor(parent, filePath.getName()); |
| if (entry != null) { |
| localTag = entry.getStickyTag(); |
| } |
| else { |
| localTag = getDirectoryTag(parent); |
| } |
| } |
| else { |
| final VirtualFile validParent = ChangesUtil.findValidParentAccurately(filePath); |
| if (validParent == null) return false; |
| localTag = getDirectoryTag(validParent); |
| } |
| final String remoteTag = changeList.getBranch(); |
| if (!Comparing.equal(localTag, remoteTag)) { |
| if (LOG.isDebugEnabled()) LOG.info(filePath + ": local tag " + localTag + ", remote tag " + remoteTag); |
| return true; |
| } |
| return false; |
| } |
| |
| @Nullable |
| private static String getDirectoryTag(@NotNull final VirtualFile parent) { |
| final String dirTag = CvsEntriesManager.getInstance().getCvsInfoFor(parent).getStickyTag(); |
| if (dirTag == null || !CvsUtil.isNonDateTag(dirTag)) return null; |
| return dirTag.substring(1); |
| } |
| |
| public boolean refreshIncomingWithCommitted() { |
| return true; |
| } |
| |
| private final ChangeListColumn<CvsChangeList> BRANCH_COLUMN = new ChangeListColumn<CvsChangeList>() { |
| public String getTitle() { |
| return CvsBundle.message("changelist.column.branch"); |
| } |
| |
| public Object getValue(final CvsChangeList changeList) { |
| final String branch = changeList.getBranch(); |
| return branch == null ? "HEAD" : branch; |
| } |
| }; |
| } |