/*******************************************************************************
* Copyright (C) 2010, 2014 Dariusz Luksza <dariusz@luksza.org> and others.
*
* 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.core.synchronize.dto;
import static org.eclipse.core.runtime.Assert.isNotNull;
import static org.eclipse.egit.core.RevUtils.getCommonAncestor;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
/**
* Simple data transfer object containing all necessary information for
* launching synchronization
*/
public class GitSynchronizeData {
private static final IWorkspaceRoot ROOT = ResourcesPlugin.getWorkspace()
.getRoot();
/**
* Matches all strings that start from R_HEADS
*/
public static final Pattern BRANCH_NAME_PATTERN = Pattern.compile("^" + R_HEADS + ".*?"); //$NON-NLS-1$ //$NON-NLS-2$
private final boolean includeLocal;
private final Repository repo;
private final String dstRemote;
private final String dstMerge;
private RevCommit srcRevCommit;
private RevCommit dstRevCommit;
private RevCommit ancestorRevCommit;
private final Set<IProject> projects;
private final String repoParentPath;
private final String srcRev;
private final String dstRev;
private TreeFilter pathFilter;
private Set<IResource> includedResources;
private static class RemoteAndMerge {
final String remote;
final String merge;
public RemoteAndMerge(String remote, String merge) {
this.remote = remote;
this.merge = merge;
}
}
/**
* Constructs {@link GitSynchronizeData} object for all resources.
* Equivalent to
* <code>new GitSynchronizeData(repository, srcRev, dstRev, includeLocal, null)</code>
* .
*
* @param repository
* @param srcRev
* @param dstRev
* @param includeLocal
* <code>true</code> if local changes should be included in
* comparison
* @throws IOException
*/
public GitSynchronizeData(Repository repository, String srcRev,
String dstRev, boolean includeLocal) throws IOException {
this(repository, srcRev, dstRev, includeLocal, null);
}
/**
* Constructs a {@link GitSynchronizeData} object while restricting it to a
* set of resources.
*
* @param repository
* @param srcRev
* @param dstRev
* @param includeLocal
* @param includedResources
* either the set of resources to include in synchronization or
* {@code null} to synchronize all resources.
* @throws IOException
*/
public GitSynchronizeData(Repository repository, String srcRev,
String dstRev, boolean includeLocal,
Set<IResource> includedResources) throws IOException {
isNotNull(repository);
isNotNull(srcRev);
isNotNull(dstRev);
this.repo = repository;
this.srcRev = srcRev;
this.dstRev = dstRev;
this.includeLocal = includeLocal;
RemoteAndMerge dstRemoteAndMerge = extractRemoteAndMergeForDst(dstRev);
dstRemote = dstRemoteAndMerge.remote;
dstMerge = dstRemoteAndMerge.merge;
repoParentPath = repo.getDirectory().getParentFile().getAbsolutePath();
projects = new HashSet<IProject>();
final Iterable<? extends IResource> includedResourceIterable;
if (includedResources == null)
// include all project in synchronization
includedResourceIterable = Arrays.asList(ROOT.getProjects());
else
includedResourceIterable = includedResources;
for (IResource res : includedResourceIterable) {
IProject project = res.getProject();
RepositoryMapping mapping = RepositoryMapping.getMapping(project);
if (mapping != null && mapping.getRepository() == repo)
projects.add(project);
}
// do not set field if includedResources is null, some methods expect
// #getIncludedResources() to return <null> to know it should
// synchronize all resources.
if (includedResources != null)
setIncludedResources(includedResources);
updateRevs();
}
/**
* Recalculates source, destination and ancestor Rev commits
*
* @throws IOException
*/
public void updateRevs() throws IOException {
ObjectWalk ow = new ObjectWalk(repo);
try {
srcRevCommit = getCommit(srcRev, ow);
dstRevCommit = getCommit(dstRev, ow);
} finally {
ow.release();
}
if (this.dstRevCommit != null && this.srcRevCommit != null)
this.ancestorRevCommit = getCommonAncestor(repo, this.srcRevCommit,
this.dstRevCommit);
else
this.ancestorRevCommit = null;
}
/**
* @return instance of repository that should be synchronized
*/
public Repository getRepository() {
return repo;
}
/**
* @return {@code null}
* @deprecated
*/
@Deprecated
public String getSrcRemoteName() {
return null;
}
/**
* @return ref specification of destination merge branch
*/
public String getDstMerge() {
return dstMerge;
}
/**
* @return {@code null}
* @deprecated
*/
@Deprecated
public String getSrcMerge() {
return null;
}
/**
* @return name of destination remote or {@code null} when destination
* branch is not a remote branch
*/
public String getDstRemoteName() {
return dstRemote;
}
/**
* @return synchronize source rev name
*/
public RevCommit getSrcRevCommit() {
return srcRevCommit;
}
/**
* @return synchronize destination rev name
*/
public RevCommit getDstRevCommit() {
return dstRevCommit;
}
/**
* @return list of project's that are connected with this repository
*/
public Set<IProject> getProjects() {
return Collections.unmodifiableSet(projects);
}
/**
* @param file
* @return <true> if given {@link File} is contained by this repository
*/
public boolean contains(File file) {
return file.getAbsoluteFile().toString().startsWith(repoParentPath);
}
/**
* @return <code>true</code> if local changes should be included in
* comparison
*/
public boolean shouldIncludeLocal() {
return includeLocal;
}
/**
* @return common ancestor commit
*/
public RevCommit getCommonAncestorRev() {
return ancestorRevCommit;
}
/**
* @param includedResources
* list of resources to be synchronized
*/
public void setIncludedResources(Set<IResource> includedResources) {
this.includedResources = includedResources;
Set<String> paths = new HashSet<String>();
RepositoryMapping rm = RepositoryMapping.findRepositoryMapping(repo);
for (IResource resource : includedResources) {
String repoRelativePath = rm.getRepoRelativePath(resource);
if (repoRelativePath != null && repoRelativePath.length() > 0)
paths.add(repoRelativePath);
}
if (!paths.isEmpty())
pathFilter = PathFilterGroup.createFromStrings(paths);
}
/**
* @return set of included resources or {@code null} when all resources
* should be included
*/
public Set<IResource> getIncludedResources() {
return includedResources;
}
/**
* Disposes all nested resources
*/
public void dispose() {
if (projects != null)
projects.clear();
if (includedResources != null)
includedResources.clear();
}
/**
* @return instance of {@link TreeFilter} when synchronization was launched
* from nested node (like folder) or {@code null} otherwise
*/
public TreeFilter getPathFilter() {
return pathFilter;
}
/**
* @return synchronization source rev
*/
public String getSrcRev() {
return srcRev;
}
/**
* @return synchronization destination rev
*/
public String getDstRev() {
return dstRev;
}
private RemoteAndMerge extractRemoteAndMergeForDst(String rev) {
// destination remote name is used for fetch and push, so check if this
// is a remote-tracking branch
try {
List<RemoteConfig> remoteConfigs = RemoteConfig
.getAllRemoteConfigs(repo.getConfig());
for (RemoteConfig remoteConfig : remoteConfigs) {
List<RefSpec> fetchRefSpecs = remoteConfig.getFetchRefSpecs();
for (RefSpec fetchRefSpec : fetchRefSpecs) {
if (fetchRefSpec.matchDestination(rev)) {
RefSpec expanded = fetchRefSpec
.expandFromDestination(rev);
return new RemoteAndMerge(remoteConfig.getName(),
expanded.getSource());
}
}
}
} catch (URISyntaxException e) {
// Fall back to returning empty result below
}
return new RemoteAndMerge(null, null);
}
private RevCommit getCommit(String rev, ObjectWalk ow) throws IOException {
if (rev.length() > 0) {
ObjectId id = repo.resolve(rev);
return id != null ? ow.parseCommit(id) : null;
} else
return null;
}
}