Package org.locationtech.geogig.remote

Source Code of org.locationtech.geogig.remote.LocalRemoteRepo

/* 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:
* Johnathan Garrett (LMN Solutions) - initial implementation
*/
package org.locationtech.geogig.remote;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;

import org.locationtech.geogig.api.Bounded;
import org.locationtech.geogig.api.Bucket;
import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.GeoGIG;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.ProgressListener;
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.RevTag;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.SymRef;
import org.locationtech.geogig.api.plumbing.ForEachRef;
import org.locationtech.geogig.api.plumbing.RefParse;
import org.locationtech.geogig.api.plumbing.UpdateRef;
import org.locationtech.geogig.api.plumbing.UpdateSymRef;
import org.locationtech.geogig.api.plumbing.diff.PostOrderDiffWalk;
import org.locationtech.geogig.api.plumbing.diff.PostOrderDiffWalk.Consumer;
import org.locationtech.geogig.api.porcelain.SynchronizationException;
import org.locationtech.geogig.repository.Repository;
import org.locationtech.geogig.storage.BulkOpListener;
import org.locationtech.geogig.storage.BulkOpListener.CountingListener;
import org.locationtech.geogig.storage.ObjectDatabase;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;

/**
* An implementation of a remote repository that exists on the local machine.
*
* @see IRemoteRepo
*/
class LocalRemoteRepo extends AbstractRemoteRepo {

    private GeoGIG remoteGeoGig;

    private Context injector;

    private File workingDirectory;

    /**
     * Constructs a new {@code LocalRemoteRepo} with the given parameters.
     *
     * @param injector the Guice injector for the new repository
     * @param workingDirectory the directory of the remote repository
     */
    public LocalRemoteRepo(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 {
        if (remoteGeoGig != null) {
            try {
                remoteGeoGig.close();
            } finally {
                remoteGeoGig = null;
            }
        }
    }

    /**
     * @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.");
        if (currHead.get().getObjectId().equals(ObjectId.NULL)) {
            return null;
        } else {
            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) {
                if (input.getObjectId().equals(ObjectId.NULL)) {
                    return false;
                }
                boolean keep = false;
                if (getHeads) {
                    keep = input.getName().startsWith(Ref.HEADS_PREFIX);
                }
                if (getTags) {
                    keep = keep || input.getName().startsWith(Ref.TAGS_PREFIX);
                }
                return keep;
            }
        };
        return remoteGeoGig.command(ForEachRef.class).setFilter(filter).call();
    }

    /**
     * Fetch all new objects from the specified {@link Ref} from the remote.
     *
     * @param ref the remote ref that points to new commit data
     * @param fetchLimit the maximum depth to fetch
     */
    @Override
    public void fetchNewData(Ref ref, Optional<Integer> fetchLimit, ProgressListener progress) {

        CommitTraverser traverser = getFetchTraverser(fetchLimit);

        try {
            progress.setDescription("Fetching objects from " + ref.getName());
            progress.setProgress(0);
            traverser.traverse(ref.getObjectId());
            List<ObjectId> toSend = new LinkedList<ObjectId>(traverser.commits);
            Collections.reverse(toSend);// send oldest commits first
            for (ObjectId newHeadId : toSend) {
                walkHead(newHeadId, true, progress);
            }

        } catch (Exception e) {
            Throwables.propagate(e);
        }
    }

    /**
     * Push all new objects from the specified {@link Ref} to the given refspec.
     *
     * @param ref the local ref that points to new commit data
     * @param refspec the refspec to push to
     */
    @Override
    public void pushNewData(final Ref ref, final String refspec, final ProgressListener progress)
            throws SynchronizationException {

        Optional<Ref> remoteRef = remoteGeoGig.command(RefParse.class).setName(refspec).call();
        remoteRef = remoteRef.or(remoteGeoGig.command(RefParse.class)
                .setName(Ref.TAGS_PREFIX + refspec).call());
        checkPush(ref, remoteRef);

        CommitTraverser traverser = getPushTraverser(remoteRef);

        traverser.traverse(ref.getObjectId());
        progress.setDescription("Uploading objects to " + refspec);
        progress.setProgress(0);
        while (!traverser.commits.isEmpty()) {
            ObjectId commitId = traverser.commits.pop();
            walkHead(commitId, false, progress);
        }

        String nameToSet = remoteRef.isPresent() ? remoteRef.get().getName() : Ref.HEADS_PREFIX
                + refspec;

        Ref updatedRef = remoteGeoGig.command(UpdateRef.class).setName(nameToSet)
                .setNewValue(ref.getObjectId()).call().get();

        Ref remoteHead = headRef();
        if (remoteHead instanceof SymRef) {
            if (((SymRef) remoteHead).getTarget().equals(updatedRef.getName())) {
                remoteGeoGig.command(UpdateSymRef.class).setName(Ref.HEAD)
                        .setNewValue(ref.getName()).call();
                RevCommit commit = remoteGeoGig.getRepository().getCommit(ref.getObjectId());
                remoteGeoGig.getRepository().workingTree().updateWorkHead(commit.getTreeId());
                remoteGeoGig.getRepository().index().updateStageHead(commit.getTreeId());
            }
        }
    }

    /**
     * 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;
    }

    protected void walkHead(final ObjectId newHeadId, final boolean fetch,
            final ProgressListener progress) {

        Repository from = localRepository;
        Repository to = remoteGeoGig.getRepository();
        if (fetch) {
            Repository tmp = to;
            to = from;
            from = tmp;
        }
        final ObjectDatabase fromDb = from.objectDatabase();
        final ObjectDatabase toDb = to.objectDatabase();

        final RevObject object = fromDb.get(newHeadId);

        RevCommit commit = null;
        RevTag tag = null;

        if (object.getType().equals(TYPE.COMMIT)) {
            commit = (RevCommit) object;
        } else if (object.getType().equals(TYPE.TAG)) {
            tag = (RevTag) object;
            commit = fromDb.getCommit(tag.getCommitId());
        }
        if (commit != null) {
            final RevTree newTree = fromDb.getTree(commit.getTreeId());
            List<ObjectId> parentIds = new ArrayList<>(commit.getParentIds());
            if (parentIds.isEmpty()) {
                parentIds.add(ObjectId.NULL);
            }
            RevTree oldTree = RevTree.EMPTY;
            // the diff against each parent is not working. For some reason some buckets that are
            // equal between the two ends of the comparison never get transferred (at some point
            // they shouldn't be equal and so the Consumer notified of it/them). Yet with the target
            // databse exists check for each tree the performance is good enough.
            // for (ObjectId parentId : parentIds) {
            // if (!parentId.isNull()) {
            // RevCommit parent = fromDb.getCommit(parentId);
            // oldTree = fromDb.getTree(parent.getTreeId());
            // }
            copyNewObjects(oldTree, newTree, fromDb, toDb, progress);
            // }
            Preconditions.checkState(toDb.exists(newTree.getId()));

            toDb.put(commit);
        }
        if (tag != null) {
            toDb.put(tag);
        }
    }

    private void copyNewObjects(RevTree oldTree, RevTree newTree, final ObjectDatabase fromDb,
            final ObjectDatabase toDb, final ProgressListener progress) {
        checkNotNull(oldTree);
        checkNotNull(newTree);
        checkNotNull(fromDb);
        checkNotNull(toDb);
        checkNotNull(progress);

        // the diff walk uses fromDb as both left and right data source since we're comparing what
        // we have in the "origin" database against trees on the same repository
        PostOrderDiffWalk diffWalk = new PostOrderDiffWalk(oldTree, newTree, fromDb, fromDb);

        // holds object ids that need to be copied to the target db. Pruned when it reaches a
        // threshold.
        final Set<ObjectId> ids = new HashSet<ObjectId>();

        // This filter further refines the post order diff walk by making it ignore trees/buckets
        // that are already present in the target db
        Predicate<Bounded> filter = new Predicate<Bounded>() {

            @Override
            public boolean apply(@Nullable Bounded b) {
                if (b == null) {
                    return false;
                }
                if (progress.isCanceled()) {
                    return false;// abort traversal
                }
                ObjectId id;
                if (b instanceof Node) {
                    Node node = (Node) b;
                    if (RevObject.TYPE.TREE.equals(node.getType())) {
                        // check of existence of trees only. For features the diff filtering is good
                        // enough and checking for existence on each feature would be killer
                        // performance wise
                        id = node.getObjectId();
                    } else {
                        return true;
                    }
                } else {
                    id = ((Bucket) b).id();
                }
                boolean exists = ids.contains(id) || toDb.exists(id);
                return !exists;
            }
        };

        // receives notifications of feature/bucket/tree diffs. Only interested in the "new"/right
        // side of the comparisons
        Consumer consumer = new Consumer() {
            final int bulkSize = 10_000;

            @Override
            public void feature(@Nullable Node left, Node right) {
                add(left);
                add(right);
            }

            @Override
            public void tree(@Nullable Node left, Node right) {
                add(left);
                add(right);
            }

            private void add(@Nullable Node node) {
                if (node == null) {
                    return;
                }
                ids.add(node.getObjectId());
                Optional<ObjectId> metadataId = node.getMetadataId();
                if (metadataId.isPresent()) {
                    ids.add(metadataId.get());
                }
                checkLimitAndCopy();
            }

            @Override
            public void bucket(int bucketIndex, int bucketDepth, @Nullable Bucket left, Bucket right) {
                if (left != null) {
                    ids.add(left.id());
                }
                if (right != null) {
                    ids.add(right.id());
                }
                checkLimitAndCopy();
            }

            private void checkLimitAndCopy() {
                if (ids.size() >= bulkSize) {
                    copy(ids, fromDb, toDb, progress);
                    ids.clear();
                }
            }
        };
        diffWalk.walk(filter, consumer);
        // copy remaining objects
        copy(ids, fromDb, toDb, progress);
    }

    private void copy(Set<ObjectId> ids, ObjectDatabase from, ObjectDatabase to,
            ProgressListener progress) {
        if (ids.isEmpty()) {
            return;
        }
        CountingListener countingListener = BulkOpListener.newCountingListener();
        to.putAll(from.getAll(ids), countingListener);
        int inserted = countingListener.inserted();
        progress.setProgress(progress.getProgress() + inserted);
    }

    /**
     * @return the {@link RepositoryWrapper} for this remote
     */
    @Override
    public RepositoryWrapper getRemoteWrapper() {
        return new LocalRepositoryWrapper(remoteGeoGig.getRepository());
    }

    /**
     * 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();
    }
}
TOP

Related Classes of org.locationtech.geogig.remote.LocalRemoteRepo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.