/* Copyright (c) 2012-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.porcelain;
import java.io.IOException;
import javax.annotation.Nullable;
import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.GlobalContextBuilder;
import org.locationtech.geogig.api.ProgressListener;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.Remote;
import org.locationtech.geogig.api.SymRef;
import org.locationtech.geogig.api.plumbing.LsRemote;
import org.locationtech.geogig.api.plumbing.RefParse;
import org.locationtech.geogig.api.plumbing.UpdateRef;
import org.locationtech.geogig.api.porcelain.ConfigOp.ConfigAction;
import org.locationtech.geogig.api.porcelain.ConfigOp.ConfigScope;
import org.locationtech.geogig.remote.IRemoteRepo;
import org.locationtech.geogig.remote.RemoteUtils;
import org.locationtech.geogig.repository.Hints;
import org.locationtech.geogig.repository.Repository;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
/**
* Clones a remote repository to a given directory.
*
*/
public class CloneOp extends AbstractGeoGigOp<Void> {
private Optional<String> branch = Optional.absent();
private String repositoryURL;
private String username = null;
private String password = null;
private Optional<Integer> depth = Optional.absent();
/**
* @param repositoryURL the URL of the repository to clone
* @return {@code this}
*/
public CloneOp setRepositoryURL(final String repositoryURL) {
this.repositoryURL = repositoryURL;
return this;
}
/**
* Get the repository URL to be cloned
*/
public Optional<String> getRepositoryURL() {
return Optional.fromNullable(repositoryURL);
}
/**
* @param username user name for the repository
* @return {@code this}
*/
public CloneOp setUserName(String username) {
this.username = username;
return this;
}
/**
* @param password password for the repository
* @return {@code this}
*/
public CloneOp setPassword(String password) {
this.password = password;
return this;
}
/**
* @param branch the branch to checkout when the clone is complete
* @return {@code this}
*/
public CloneOp setBranch(@Nullable String branch) {
this.branch = Optional.fromNullable(branch);
return this;
}
public Optional<String> getBranch() {
return branch;
}
/**
* @param depth the depth of the clone, if depth is < 1, then a full clone s performed
* @return {@code this}
*/
public CloneOp setDepth(final int depth) {
if (depth > 0) {
this.depth = Optional.of(depth);
}
return this;
}
public Optional<Integer> getDepth() {
return depth;
}
/**
* Executes the clone operation.
*
* @return {@code null}
* @see org.locationtech.geogig.api.AbstractGeoGigOp#call()
*/
@Override
protected Void _call() {
Preconditions.checkArgument(repositoryURL != null && !repositoryURL.isEmpty(),
"No repository specified to clone from.");
Repository repository = repository();
if (repository.isSparse()) {
Preconditions
.checkArgument(branch.isPresent(), "No branch specified for sparse clone.");
}
ProgressListener progressListener = getProgressListener();
progressListener.started();
// Set up origin
Remote remote = command(RemoteAddOp.class).setName("origin").setURL(repositoryURL)
.setMapped(repository.isSparse()).setUserName(username).setPassword(password)
.setBranch(repository.isSparse() ? branch.get() : null).call();
if (!depth.isPresent()) {
// See if we are cloning a shallow clone. If so, a depth must be specified.
Optional<IRemoteRepo> remoteRepo = RemoteUtils.newRemote(
GlobalContextBuilder.builder.build(Hints.readOnly()), remote, repository,
repository.deduplicationService());
Preconditions.checkState(remoteRepo.isPresent(), "Failed to connect to the remote.");
IRemoteRepo remoteRepoInstance = remoteRepo.get();
try {
remoteRepoInstance.open();
} catch (IOException e) {
Throwables.propagate(e);
}
try {
depth = remoteRepoInstance.getDepth();
} finally {
try {
remoteRepoInstance.close();
} catch (IOException e) {
Throwables.propagate(e);
}
}
}
if (depth.isPresent()) {
command(ConfigOp.class).setAction(ConfigAction.CONFIG_SET).setScope(ConfigScope.LOCAL)
.setName(Repository.DEPTH_CONFIG_KEY).setValue(depth.get().toString()).call();
}
// Fetch remote data
command(FetchOp.class).setDepth(depth.or(0)).setProgressListener(progressListener).call();
// Set up remote tracking branches
final ImmutableSet<Ref> remoteRefs = command(LsRemote.class).retrieveTags(false)
.setRemote(Suppliers.ofInstance(Optional.of(remote))).call();
boolean emptyRepo = true;
for (Ref remoteRef : remoteRefs) {
if (emptyRepo && !remoteRef.getObjectId().isNull()) {
emptyRepo = false;
}
String branchName = remoteRef.localName();
if (remoteRef instanceof SymRef) {
continue;
}
if (!command(RefParse.class).setName(Ref.HEADS_PREFIX + branchName).call().isPresent()) {
command(BranchCreateOp.class).setName(branchName)
.setSource(remoteRef.getObjectId().toString()).call();
} else {
command(UpdateRef.class).setName(Ref.HEADS_PREFIX + branchName)
.setNewValue(remoteRef.getObjectId()).call();
}
command(ConfigOp.class).setAction(ConfigAction.CONFIG_SET).setScope(ConfigScope.LOCAL)
.setName("branches." + branchName + ".remote").setValue(remote.getName())
.call();
command(ConfigOp.class).setAction(ConfigAction.CONFIG_SET).setScope(ConfigScope.LOCAL)
.setName("branches." + branchName + ".merge")
.setValue(Ref.HEADS_PREFIX + remoteRef.localName()).call();
}
if (!emptyRepo) {
// checkout branch
if (branch.isPresent()) {
command(CheckoutOp.class).setForce(true).setSource(branch.get()).call();
} else {
// checkout the head
final Optional<Ref> currRemoteHead = command(RefParse.class).setName(
Ref.REMOTES_PREFIX + remote.getName() + "/" + Ref.HEAD).call();
if (currRemoteHead.isPresent() && currRemoteHead.get() instanceof SymRef) {
final SymRef remoteHeadRef = (SymRef) currRemoteHead.get();
final String currentBranch = Ref.localName(remoteHeadRef.getTarget());
command(CheckoutOp.class).setForce(true).setSource(currentBranch).call();
} else {
// just leave at default; should be master since we just initialized the repo.
}
}
}
progressListener.complete();
return null;
}
}