/*******************************************************************************
* Copyright (c) 2010, 2014 SAP AG and others.
* Copyright (C) 2012, 2013 Tomasz Zarna <tzarna@gmail.com>
* Copyright (C) 2014 Axel Richard <axel.richard@obeo.fr>
* Copyright (C) 2015 Obeo
* Copyright (C) 2015, Stephan Hackstedt <stephan.hackstedt@googlemail.com>
*
* 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
*
* Contributors:
* Stefan Lay (SAP AG) - initial implementation
* Tomasz Zarna (IBM) - merge squash, bug 382720
* Axel Richard (Obeo) - merge message, bug 422886
* Laurent Delaigue (Obeo) - use of preferred merge strategy
* Stephan Hackstedt - bug 477695
*******************************************************************************/
package org.eclipse.egit.core.op;
import java.io.IOException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.EclipseGitProgressTransformer;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.egit.core.internal.job.RuleUtil;
import org.eclipse.egit.core.internal.util.ProjectUtil;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeCommand;
import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
/**
* This class implements the merge of a ref with the current head
*
*/
public class MergeOperation implements IEGitOperation {
private final Repository repository;
private final String refName;
private final MergeStrategy mergeStrategy;
private Boolean squash;
private FastForwardMode fastForwardMode;
private Boolean commit;
private MergeResult mergeResult;
private String message;
/**
* Create a MergeOperation object. Initializes the MergeStrategy with the
* preferred merge strategy, according to preferences.
*
* @param repository
* @param refName
* name of a commit which should be merged
*/
public MergeOperation(@NonNull Repository repository,
@NonNull String refName) {
this.repository = repository;
this.refName = refName;
this.mergeStrategy = Activator.getDefault().getPreferredMergeStrategy();
}
/**
* Create a MergeOperation object
*
* @param repository
* @param refName
* name of a commit which should be merged
* @param mergeStrategyName
* the strategy to use for merge. If not registered, the default
* merge strategy according to preferences will be used.
*/
public MergeOperation(@NonNull Repository repository,
@NonNull String refName,
@NonNull String mergeStrategyName) {
this.repository = repository;
this.refName = refName;
MergeStrategy strategy = null;
strategy = MergeStrategy.get(mergeStrategyName);
this.mergeStrategy = strategy != null ? strategy : Activator.getDefault()
.getPreferredMergeStrategy();
}
/**
* @param squash true to squash merge commits
*/
public void setSquash(boolean squash) {
this.squash = Boolean.valueOf(squash);
}
/**
* @param ffmode set the fast forward mode
* @since 3.0
*/
public void setFastForwardMode(FastForwardMode ffmode) {
this.fastForwardMode = ffmode;
}
/**
* @param commit
* set the commit option
* @since 3.1
*/
public void setCommit(boolean commit) {
this.commit = Boolean.valueOf(commit);
}
/**
* Set the commit message to be used for the merge commit (in case one is
* created)
*
* @param message
* the message to be used for the merge commit
*/
public void setMessage(String message) {
this.message = message;
}
@Override
public void execute(IProgressMonitor m) throws CoreException {
if (mergeResult != null)
throw new CoreException(new Status(IStatus.ERROR, Activator
.getPluginId(), CoreText.OperationAlreadyExecuted));
IWorkspaceRunnable action = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor mymonitor) throws CoreException {
IProject[] validProjects = ProjectUtil.getValidOpenProjects(repository);
SubMonitor progress = SubMonitor.convert(mymonitor, NLS.bind(
CoreText.MergeOperation_ProgressMerge, refName), 3);
try (Git git = new Git(repository)) {
progress.worked(1);
MergeCommand merge = git.merge().setProgressMonitor(
new EclipseGitProgressTransformer(
progress.newChild(1)));
Ref ref = repository.findRef(refName);
if (ref != null) {
merge.include(ref);
} else {
merge.include(ObjectId.fromString(refName));
}
if (fastForwardMode != null) {
merge.setFastForward(fastForwardMode);
}
if (commit != null) {
merge.setCommit(commit.booleanValue());
}
if (squash != null) {
merge.setSquash(squash.booleanValue());
}
if (mergeStrategy != null) {
merge.setStrategy(mergeStrategy);
}
if (message != null) {
merge.setMessage(message);
}
mergeResult = merge.call();
if (MergeResult.MergeStatus.NOT_SUPPORTED
.equals(mergeResult.getMergeStatus())) {
throw new TeamException(new Status(IStatus.INFO,
Activator.getPluginId(),
mergeResult.toString()));
}
} catch (IOException e) {
throw new TeamException(
CoreText.MergeOperation_InternalError, e);
} catch (NoHeadException e) {
throw new TeamException(
CoreText.MergeOperation_MergeFailedNoHead, e);
} catch (ConcurrentRefUpdateException e) {
throw new TeamException(
CoreText.MergeOperation_MergeFailedRefUpdate, e);
} catch (CheckoutConflictException e) {
mergeResult = new MergeResult(e.getConflictingPaths());
return;
} catch (GitAPIException e) {
throw new TeamException(e.getLocalizedMessage(),
e.getCause());
} finally {
ProjectUtil.refreshValidProjects(validProjects,
progress.newChild(1));
}
}
};
// lock workspace to protect working tree changes
ResourcesPlugin.getWorkspace().run(action, getSchedulingRule(),
IWorkspace.AVOID_UPDATE, m);
}
/**
* @return the merge result, or <code>null</code> if this has not been
* executed or if an exception occurred
*/
public @Nullable MergeResult getResult() {
return this.mergeResult;
}
@Override
public ISchedulingRule getSchedulingRule() {
return RuleUtil.getRule(repository);
}
}