/* Copyright (c) 2013-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: * Johnathan Garrett (LMN Solutions) - initial implementation */ package org.locationtech.geogig.remote; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.locationtech.geogig.api.CommitBuilder; import org.locationtech.geogig.api.Context; import org.locationtech.geogig.api.GeoGIG; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.RevCommit; import org.locationtech.geogig.api.RevObject; import org.locationtech.geogig.api.RevObject.TYPE; import org.locationtech.geogig.api.RevTree; import org.locationtech.geogig.api.SymRef; import org.locationtech.geogig.api.plumbing.CheckSparsePath; import org.locationtech.geogig.api.plumbing.FindCommonAncestor; import org.locationtech.geogig.api.plumbing.ForEachRef; import org.locationtech.geogig.api.plumbing.RefParse; import org.locationtech.geogig.api.plumbing.ResolveTreeish; import org.locationtech.geogig.api.plumbing.RevObjectParse; import org.locationtech.geogig.api.plumbing.UpdateRef; import org.locationtech.geogig.api.plumbing.UpdateSymRef; import org.locationtech.geogig.api.plumbing.WriteTree; import org.locationtech.geogig.api.plumbing.diff.DiffEntry; import org.locationtech.geogig.api.porcelain.DiffOp; import org.locationtech.geogig.repository.Repository; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableSet; /** * An implementation of a remote repository that exists on the local machine. * * @see IRemoteRepo */ public class LocalMappedRemoteRepo extends AbstractMappedRemoteRepo { private GeoGIG remoteGeoGig; private Context injector; private File workingDirectory; /** * Constructs a new {@code MappedLocalRemoteRepo} with the given parameters. * * @param injector the Guice injector for the new repository * @param workingDirectory the directory of the remote repository */ public LocalMappedRemoteRepo(Context injector, File workingDirectory, Repository localRepository) { super(localRepository); this.injector = injector; this.workingDirectory = workingDirectory; } /** * @param geogig manually set a geogig for this remote repository */ public void setGeoGig(GeoGIG geogig) { this.remoteGeoGig = geogig; } /** * Opens the remote repository. * * @throws IOException */ @Override public void open() throws IOException { if (remoteGeoGig == null) { remoteGeoGig = new GeoGIG(injector, workingDirectory); remoteGeoGig.getRepository(); } } /** * Closes the remote repository. * * @throws IOException */ @Override public void close() throws IOException { remoteGeoGig.close(); } /** * @return the remote's HEAD {@link Ref}. */ @Override public Ref headRef() { final Optional<Ref> currHead = remoteGeoGig.command(RefParse.class).setName(Ref.HEAD) .call(); Preconditions.checkState(currHead.isPresent(), "Remote repository has no HEAD."); return currHead.get(); } /** * List the remote's {@link Ref refs}. * * @param getHeads whether to return refs in the {@code refs/heads} namespace * @param getTags whether to return refs in the {@code refs/tags} namespace * @return an immutable set of refs from the remote */ @Override public ImmutableSet<Ref> listRefs(final boolean getHeads, final boolean getTags) { Predicate<Ref> filter = new Predicate<Ref>() { @Override public boolean apply(Ref input) { boolean keep = false; if (getHeads) { keep = input.getName().startsWith(Ref.HEADS_PREFIX); } if (getTags) { keep = keep || input.getName().startsWith(Ref.TAGS_PREFIX); } return keep; } }; ImmutableSet<Ref> remoteRefs = remoteGeoGig.command(ForEachRef.class).setFilter(filter) .call(); // Translate the refs to their mapped values. ImmutableSet.Builder<Ref> builder = new ImmutableSet.Builder<Ref>(); for (Ref remoteRef : remoteRefs) { Ref newRef = remoteRef; if (!(newRef instanceof SymRef) && localRepository.graphDatabase().exists(remoteRef.getObjectId())) { ObjectId mappedCommit = localRepository.graphDatabase().getMapping( remoteRef.getObjectId()); if (mappedCommit != null) { newRef = new Ref(remoteRef.getName(), mappedCommit); } } builder.add(newRef); } return builder.build(); } /** * Delete the given refspec from the remote repository. * * @param refspec the refspec to delete */ @Override public Optional<Ref> deleteRef(String refspec) { Optional<Ref> deletedRef = remoteGeoGig.command(UpdateRef.class).setName(refspec) .setDelete(true).call(); return deletedRef; } /** * Gets the remote ref that matches the provided ref spec. * * @param refspec the refspec to parse * @return the matching {@link Ref} or {@link Optional#absent()} if the ref could not be found */ @Override protected Optional<Ref> getRemoteRef(String refspec) { return remoteGeoGig.command(RefParse.class).setName(refspec).call(); } /** * Updates the remote ref that matches the given refspec. * * @param refspec the ref to update * @param commitId the new value of the ref * @param delete if true, the remote ref will be deleted * @return the updated ref */ @Override protected Optional<Ref> updateRemoteRef(String refspec, ObjectId commitId, boolean delete) { Optional<Ref> updatedRef = remoteGeoGig.command(UpdateRef.class).setName(refspec) .setNewValue(commitId).setDelete(delete).call(); if (updatedRef.isPresent()) { final Ref remoteHead = headRef(); if (remoteHead instanceof SymRef) { if (((SymRef) remoteHead).getTarget().equals(updatedRef.get().getName())) { remoteGeoGig.command(UpdateSymRef.class).setName(Ref.HEAD) .setNewValue(updatedRef.get().getName()).call(); RevCommit commit = remoteGeoGig.getRepository().getCommit(commitId); remoteGeoGig.getRepository().workingTree().updateWorkHead(commit.getTreeId()); remoteGeoGig.getRepository().index().updateStageHead(commit.getTreeId()); } } } return updatedRef; } /** * This function takes all of the changes introduced by a commit on the sparse repository and * creates a new commit on the full repository with those changes. * * @param commitId the commit id of commit from the sparse repository * @param from the sparse repository * @param to the full repository */ protected void pushSparseCommit(ObjectId commitId) { Repository from = localRepository; Repository to = remoteGeoGig.getRepository(); Optional<RevObject> object = from.command(RevObjectParse.class).setObjectId(commitId) .call(); if (object.isPresent() && object.get().getType().equals(TYPE.COMMIT)) { RevCommit commit = (RevCommit) object.get(); ObjectId parent = ObjectId.NULL; List<ObjectId> newParents = new LinkedList<ObjectId>(); for (int i = 0; i < commit.getParentIds().size(); i++) { ObjectId parentId = commit.getParentIds().get(i); if (i != 0) { Optional<ObjectId> commonAncestor = from.command(FindCommonAncestor.class) .setLeftId(commit.getParentIds().get(0)).setRightId(parentId).call(); if (commonAncestor.isPresent()) { if (from.command(CheckSparsePath.class).setStart(parentId) .setEnd(commonAncestor.get()).call()) { // This should be the base commit to preserve the sparse changes that // were filtered // out. newParents.add(0, from.graphDatabase().getMapping(parentId)); continue; } } } newParents.add(from.graphDatabase().getMapping(parentId)); } if (newParents.size() > 0) { parent = from.graphDatabase().getMapping(newParents.get(0)); } Iterator<DiffEntry> diffIter = from.command(DiffOp.class).setNewVersion(commitId) .setOldVersion(parent).setReportTrees(true).call(); LocalCopyingDiffIterator changes = new LocalCopyingDiffIterator(diffIter, from, to); RevTree rootTree = RevTree.EMPTY; if (newParents.size() > 0) { ObjectId mappedCommit = newParents.get(0); Optional<ObjectId> treeId = to.command(ResolveTreeish.class) .setTreeish(mappedCommit).call(); if (treeId.isPresent()) { rootTree = to.getTree(treeId.get()); } } // Create new commit ObjectId newTreeId = to.command(WriteTree.class) .setOldRoot(Suppliers.ofInstance(rootTree)) .setDiffSupplier(Suppliers.ofInstance((Iterator<DiffEntry>) changes)).call(); CommitBuilder builder = new CommitBuilder(commit); builder.setParentIds(newParents); builder.setTreeId(newTreeId); RevCommit mapped = builder.build(); to.objectDatabase().put(mapped); from.graphDatabase().map(commit.getId(), mapped.getId()); from.graphDatabase().map(mapped.getId(), commit.getId()); } } /** * @return the {@link RepositoryWrapper} for this remote */ @Override public RepositoryWrapper getRemoteWrapper() { return new LocalRepositoryWrapper(remoteGeoGig.getRepository()); } /** * Retrieves an object with the specified id from the remote. * * @param objectId the object to get * @return the fetched object */ @Override protected Optional<RevObject> getObject(ObjectId objectId) { return remoteGeoGig.command(RevObjectParse.class).setObjectId(objectId).call(); } /** * Gets all of the changes from the target commit that should be applied to the sparse clone. * * @param commit the commit to get changes from * @return an iterator for changes that match the repository filter */ @Override protected FilteredDiffIterator getFilteredChanges(RevCommit commit) { ObjectId parent = ObjectId.NULL; if (commit.getParentIds().size() > 0) { parent = commit.getParentIds().get(0); } Iterator<DiffEntry> changes = remoteGeoGig.command(DiffOp.class) .setNewVersion(commit.getId()).setOldVersion(parent).setReportTrees(true).call(); return new LocalFilteredDiffIterator(changes, remoteGeoGig.getRepository(), localRepository, filter); } /** * Gets the depth of the remote repository. * * @return the depth of the repository, or {@link Optional#absent()} if the repository is not * shallow */ @Override public Optional<Integer> getDepth() { return remoteGeoGig.getRepository().getDepth(); } }