/*******************************************************************************
* Copyright (c) 2000, 2004 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.ui.subscriber;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.Command;
import org.eclipse.team.internal.ccvs.core.client.Commit;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.operations.CommitOperation;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPart;
/**
* A commit set is used to associate a comment with a set of outgoing
* file modifications. If the comment of the set is null
,
* the title of the commit set will be used as the comment when committing
*/
public class CommitSet {
private static final String CTX_REVISION = "revision"; //$NON-NLS-1$
private static final String CTX_PATH = "path"; //$NON-NLS-1$
private static final String CTX_FILES = "files"; //$NON-NLS-1$
private static final String CTX_TITLE = "title"; //$NON-NLS-1$
private static final String CTX_COMMENT = "comment"; //$NON-NLS-1$
private String title;
private String comment;
private Map dirtyFiles; // Maps IFile->String(revision)
/**
* Restore a commit set from the given memento
* @param memento the memento to which the set was saved
* @return the restored set
*/
public static CommitSet from(IMemento memento) {
CommitSet set = new CommitSet();
set.init(memento);
return set;
}
private CommitSet() {
dirtyFiles = new HashMap();
}
/**
* Create a commit set with the given title.
* @param title the title for the commit set
*/
/* package */ CommitSet(String title) {
this();
this.title = title;
}
/**
* Get the title of the commit set. The title is used
* as the comment when the set is committed if no comment
* has been explicitly set using setComment
.
* @return the title of the set
*/
public String getTitle() {
return title;
}
/**
* Set the title of the set. The title is used
* as the comment when the set is committed if no comment
* has been explicitly set using setComment
.
* @param title the title of the set
*/
public void setTitle(String title) {
this.title = title;
CommitSetManager.getInstance().titleChanged(this);
}
/**
* Get the comment of this commit set. If the comment
* as never been set, the title is returned as the comment
* @return the comment to be used when the set is committed
*/
public String getComment() {
if (comment == null) {
return getTitle();
}
return comment;
}
/**
* Set the comment to be used when the commit set is committed.
* If null
is passed, the title of the set
* will be used as the comment.
* @param comment the comment for the set or null
* if the title should be the comment
*/
public void setComment(String comment) {
if (comment != null && comment.equals(getTitle())) {
this.comment = null;
} else {
this.comment = comment;
}
}
/**
* Add the dirty files in the given array to the commit set.
* The list of files that were added is returned.
* @param files the files to be added to the set
* @return the files that were added because they were dirty
* @throws CVSException if the dirty state or revision of one of the files could not be determined
*/
public IFile[] addFiles(IResource[] files) throws CVSException {
List addedFiles = new ArrayList();
for (int i = 0; i < files.length; i++) {
IResource file = files[i];
if (file.getType() == IResource.FILE && addFile((IFile)file)) {
addedFiles.add(file);
}
}
IFile[] fileArray = (IFile[]) addedFiles.toArray(new IFile[addedFiles.size()]);
if (fileArray.length > 0) {
CommitSetManager.getInstance().filesAdded(this, fileArray);
}
return fileArray;
}
/**
* Remove the given files from this set.
* @param files the files to be removed
*/
public void removeFiles(IFile[] files) {
List removed = new ArrayList();
for (int i = 0; i < files.length; i++) {
IFile file = files[i];
if (dirtyFiles.remove(file) != null) {
removed.add(file);
}
}
if (!removed.isEmpty()) {
CommitSetManager.getInstance().filesChanged(this, (IFile[]) removed.toArray(new IFile[removed.size()]));
}
}
private boolean addFile(IFile file) throws CVSException {
ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor(file);
if (!cvsFile.isModified(null)) {
return false;
}
byte[] syncBytes = cvsFile.getSyncBytes();
String revision;
if (syncBytes == null) {
revision = ResourceSyncInfo.ADDED_REVISION;
} else {
revision = ResourceSyncInfo.getRevision(syncBytes);
}
addFile(file, revision);
return true;
}
private boolean isModified(IResource resource) {
try {
ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
return cvsResource.isModified(null);
} catch (CVSException e) {
CVSUIPlugin.log(e);
return true;
}
}
private void addFile(IFile file, String revision) {
dirtyFiles.put(file, revision);
}
private String getRevision(IFile file) {
return (String)dirtyFiles.get(file);
}
public void save(IMemento memento) {
memento.putString(CTX_TITLE, getTitle());
if (comment != null) {
memento.putString(CTX_COMMENT, comment);
}
for (Iterator iter = dirtyFiles.keySet().iterator(); iter.hasNext();) {
IFile file = (IFile) iter.next();
IMemento child = memento.createChild(CTX_FILES);
child.putString(CTX_PATH, file.getFullPath().toString());
child.putString(CTX_REVISION, getRevision(file));
}
}
public void init(IMemento memento) {
title = memento.getString(CTX_TITLE);
comment = memento.getString(CTX_COMMENT);
IMemento[] children = memento.getChildren(CTX_FILES);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
for (int i = 0; i < children.length; i++) {
IMemento child = children[i];
String path = child.getString(CTX_PATH);
String revision = child.getString(CTX_REVISION);
IFile file = root.getFile(new Path(path));
addFile(file, revision);
}
}
/**
* The given project is no longer under CVS control.
* Remoev any files that may have been included in the
* commit set.
*/
/* package*/ void projectRemoved(IProject project) {
List filesToRemove = new ArrayList();
for (Iterator iter = dirtyFiles.keySet().iterator(); iter.hasNext();) {
IFile file = (IFile) iter.next();
if (file.getProject().equals(project)) {
filesToRemove.add(file);
}
}
removeFiles((IFile[]) filesToRemove.toArray(new IFile[filesToRemove.size()]));
}
/**
* The sync state of the given resources has changed. If any of them are in this
* set, adjust the state accordingly.
* @param changedResources the resources whose sync state has changed
*/
/* package*/ void resourceSyncInfoChanged(IResource[] changedResources) {
List filesToRemove = new ArrayList();
for (int i = 0; i < changedResources.length; i++) {
IResource resource = changedResources[i];
if (dirtyFiles.containsKey(resource) && !isModified(resource)) {
filesToRemove.add(resource);
}
}
removeFiles((IFile[]) filesToRemove.toArray(new IFile[filesToRemove.size()]));
}
/**
* Commit the files in this commit set to the repository.
* @param monitor a progress monitor
* @throws InterruptedException
* @throws InvocationTargetException
*/
public void commit(IWorkbenchPart part, IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
IFile[] files = getFiles();
new CommitOperation(part, files, new Command.LocalOption[] { Commit.makeArgumentOption(Command.MESSAGE_OPTION, getComment()) })
.run(monitor);
// TODO: Handle set archival
}
/**
* Return the dirty files contained in this set.
* @return the dirty files contained in this set
*/
public IFile[] getFiles() {
return (IFile[]) dirtyFiles.keySet().toArray(new IFile[dirtyFiles.size()]);
}
/**
* Return whether the set contains any files.
* @return whether the set contains any files
*/
public boolean isEmpty() {
return dirtyFiles.isEmpty();
}
/**
* Return true if the given file is included in this set.
* @param local a ocal file
* @return true if the given file is included in this set
*/
public boolean contains(IResource local) {
return dirtyFiles.containsKey(local);
}
/**
* Return whether the set has a comment that differs from the title.
* @return whether the set has a comment that differs from the title
*/
public boolean hasComment() {
return comment != null;
}
}