/*******************************************************************************
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2007, Jing Xue <jingxue@digizenstudio.com>
* Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
* Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com>
* Copyright (C) 2012, Robin Stocker <robin@nibor.org>
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.egit.ui.internal.commit;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.EclipseGitProgressTransformer;
import org.eclipse.egit.core.IteratorService;
import org.eclipse.egit.core.op.CommitOperation;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.dialogs.BasicConfigurationDialog;
import org.eclipse.egit.ui.internal.dialogs.CommitDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.IndexDiff;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
/**
* UI component for performing a commit
*/
public class CommitUI {
private IndexDiff indexDiff;
private Set<String> notIndexed;
private Set<String> indexChanges;
private Set<String> notTracked;
private Set<String> files;
private boolean amending;
private Shell shell;
private Repository repo;
private IResource[] selectedResources;
private boolean preselectAll;
/**
* Constructs a CommitUI object
* @param shell
* Shell to use for UI interaction. Must not be null.
* @param repo
* Repository to commit. Must not be null
* @param selectedResources
* Resources selected by the user. A file is preselected in the
* commit dialog if the file is contained in selectedResources or
* if selectedResources contains a resource that is parent of the
* file. selectedResources must not be null.
* @param preselectAll
* preselect all changed files in the commit dialog.
* If set to true selectedResources are ignored.
*/
public CommitUI(Shell shell, Repository repo,
IResource[] selectedResources, boolean preselectAll) {
this.shell = shell;
this.repo = repo;
this.selectedResources = new IResource[selectedResources.length];
// keep our own copy
System.arraycopy(selectedResources, 0, this.selectedResources, 0,
selectedResources.length);
this.preselectAll = preselectAll;
}
/**1
* Performs a commit
* @return true if a commit operation was triggered
*/
public boolean commit() {
// let's see if there is any dirty editor around and
// ask the user if they want to save or abort
if (!UIUtils.saveAllEditors(repo))
return false;
BasicConfigurationDialog.show(new Repository[]{repo});
resetState();
final IProject[] projects = getProjectsOfRepositories();
try {
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException,
InterruptedException {
try {
buildIndexHeadDiffList(projects, monitor);
} catch (IOException e) {
throw new InvocationTargetException(e);
}
}
});
} catch (InvocationTargetException e) {
Activator.handleError(UIText.CommitAction_errorComputingDiffs, e.getCause(),
true);
return false;
} catch (InterruptedException e) {
return false;
}
CommitHelper commitHelper = new CommitHelper(repo);
if (!commitHelper.canCommit()) {
MessageDialog.openError(
shell,
UIText.CommitAction_cannotCommit,
commitHelper.getCannotCommitMessage());
return false;
}
boolean amendAllowed = commitHelper.amendAllowed();
if (files.isEmpty()) {
if (amendAllowed && commitHelper.getPreviousCommit() != null) {
boolean result = MessageDialog.openQuestion(shell,
UIText.CommitAction_noFilesToCommit,
UIText.CommitAction_amendCommit);
if (!result)
return false;
amending = true;
} else {
MessageDialog.openWarning(shell,
UIText.CommitAction_noFilesToCommit,
UIText.CommitAction_amendNotPossible);
return false;
}
}
CommitDialog commitDialog = new CommitDialog(shell);
commitDialog.setAmending(amending);
commitDialog.setAmendAllowed(amendAllowed);
commitDialog.setFiles(repo, files, indexDiff);
commitDialog.setPreselectedFiles(getSelectedFiles());
commitDialog.setPreselectAll(preselectAll);
commitDialog.setAuthor(commitHelper.getAuthor());
commitDialog.setCommitter(commitHelper.getCommitter());
commitDialog.setAllowToChangeSelection(!commitHelper.isMergedResolved && !commitHelper.isCherryPickResolved);
commitDialog.setCommitMessage(commitHelper.getCommitMessage());
if (commitDialog.open() != IDialogConstants.OK_ID)
return false;
final CommitOperation commitOperation;
try {
commitOperation= new CommitOperation(
repo,
commitDialog.getSelectedFiles(), notTracked, commitDialog.getAuthor(),
commitDialog.getCommitter(), commitDialog.getCommitMessage());
} catch (CoreException e1) {
Activator.handleError(UIText.CommitUI_commitFailed, e1, true);
return false;
}
if (commitDialog.isAmending())
commitOperation.setAmending(true);
commitOperation.setComputeChangeId(commitDialog.getCreateChangeId());
commitOperation.setCommitAll(commitHelper.isMergedResolved);
if (commitHelper.isMergedResolved)
commitOperation.setRepository(repo);
Job commitJob = new CommitJob(repo, commitOperation).
setPushUpstream(commitDialog.isPushRequested());
commitJob.schedule();
return true;
}
private IProject[] getProjectsOfRepositories() {
Set<IProject> ret = new HashSet<IProject>();
final IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
.getProjects();
for (IProject project : projects) {
RepositoryMapping mapping = RepositoryMapping.getMapping(project);
if (mapping != null && mapping.getRepository() == repo)
ret.add(project);
}
return ret.toArray(new IProject[ret.size()]);
}
private void resetState() {
files = new LinkedHashSet<String>();
notIndexed = new LinkedHashSet<String>();
indexChanges = new LinkedHashSet<String>();
notTracked = new LinkedHashSet<String>();
amending = false;
indexDiff = null;
}
/**
* Retrieves a collection of files that may be committed based on the user's
* selection when they performed the commit action. That is, even if the
* user only selected one folder when the action was performed, if the
* folder contains any files that could be committed, they will be returned.
*
* @return a collection of files that is eligible to be committed based on
* the user's selection
*/
private Set<String> getSelectedFiles() {
Set<String> preselectionCandidates = new LinkedHashSet<String>();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
// iterate through all the files that may be committed
for (String fileName : files) {
URI uri = new File(repo.getWorkTree(), fileName).toURI();
IFile[] workspaceFiles = root.findFilesForLocationURI(uri);
if (workspaceFiles.length > 0) {
IFile file = workspaceFiles[0];
for (IResource resource : selectedResources) {
// if any selected resource contains the file, add it as a
// preselection candidate
if (resource.contains(file)) {
preselectionCandidates.add(fileName);
break;
}
}
} else {
// could be file outside of workspace
for (IResource resource : selectedResources) {
if(resource.getFullPath().toFile().equals(new File(uri))) {
preselectionCandidates.add(fileName);
}
}
}
}
return preselectionCandidates;
}
private void buildIndexHeadDiffList(IProject[] selectedProjects,
IProgressMonitor monitor) throws IOException,
OperationCanceledException {
monitor.beginTask(UIText.CommitActionHandler_calculatingChanges, 1000);
EclipseGitProgressTransformer jgitMonitor = new EclipseGitProgressTransformer(
monitor);
CountingVisitor counter = new CountingVisitor();
for (IProject p : selectedProjects) {
try {
p.accept(counter);
} catch (CoreException e) {
// ignore
}
}
WorkingTreeIterator it = IteratorService.createInitialIterator(repo);
if (it == null)
throw new OperationCanceledException(); // workspace is closed
indexDiff = new IndexDiff(repo, Constants.HEAD, it);
indexDiff.diff(jgitMonitor, counter.count, 0, NLS.bind(
UIText.CommitActionHandler_repository, repo.getDirectory()
.getPath()));
includeList(indexDiff.getAdded(), indexChanges);
includeList(indexDiff.getChanged(), indexChanges);
includeList(indexDiff.getRemoved(), indexChanges);
includeList(indexDiff.getMissing(), notIndexed);
includeList(indexDiff.getModified(), notIndexed);
includeList(indexDiff.getUntracked(), notTracked);
if (monitor.isCanceled())
throw new OperationCanceledException();
monitor.done();
}
static class CountingVisitor implements IResourceVisitor {
int count;
public boolean visit(IResource resource) throws CoreException {
count++;
return true;
}
}
private void includeList(Set<String> added, Set<String> category) {
for (String filename : added) {
if (!files.contains(filename))
files.add(filename);
category.add(filename);
}
}
}