Package org.locationtech.geogig.api.porcelain

Source Code of org.locationtech.geogig.api.porcelain.FetchOp

/* 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.api.porcelain;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.GlobalContextBuilder;
import org.locationtech.geogig.api.ObjectId;
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.plumbing.UpdateSymRef;
import org.locationtech.geogig.api.porcelain.ConfigOp.ConfigAction;
import org.locationtech.geogig.api.porcelain.ConfigOp.ConfigScope;
import org.locationtech.geogig.api.porcelain.TransferSummary.ChangedRef;
import org.locationtech.geogig.api.porcelain.TransferSummary.ChangedRef.ChangeTypes;
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 org.locationtech.geogig.storage.DeduplicationService;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;

/**
* Fetches named heads or tags from one or more other repositories, along with the objects necessary
* to complete them.
*/
public class FetchOp extends AbstractGeoGigOp<TransferSummary> {

    private boolean all;

    private boolean prune;

    private boolean fullDepth = false;

    private List<Remote> remotes = new ArrayList<Remote>();

    private Optional<Integer> depth = Optional.absent();

    /**
     * @param all if {@code true}, fetch from all remotes.
     * @return {@code this}
     */
    public FetchOp setAll(final boolean all) {
        this.all = all;
        return this;
    }

    public boolean isAll() {
        return all;
    }

    /**
     * @param prune if {@code true}, remote tracking branches that no longer exist will be removed
     *        locally.
     * @return {@code this}
     */
    public FetchOp setPrune(final boolean prune) {
        this.prune = prune;
        return this;
    }

    public boolean isPrune() {
        return prune;
    }

    /**
     * If no depth is specified, fetch will pull all history from the specified ref(s). If the
     * repository is shallow, it will maintain the existing depth.
     *
     * @param depth maximum commit depth to fetch
     * @return {@code this}
     */
    public FetchOp setDepth(final int depth) {
        if (depth > 0) {
            this.depth = Optional.of(depth);
        }
        return this;
    }

    public Integer getDepth() {
        return this.depth.orNull();
    }

    /**
     * If full depth is set on a shallow clone, then the full history will be fetched.
     *
     * @param fulldepth whether or not to fetch the full history
     * @return {@code this}
     */
    public FetchOp setFullDepth(boolean fullDepth) {
        this.fullDepth = fullDepth;
        return this;
    }

    public boolean isFullDepth() {
        return fullDepth;
    }

    /**
     * @param remoteName the name or URL of a remote repository to fetch from
     * @return {@code this}
     */
    public FetchOp addRemote(final String remoteName) {
        Preconditions.checkNotNull(remoteName);
        return addRemote(command(RemoteResolve.class).setName(remoteName));
    }

    public List<String> getRemoteNames() {
        return Lists.transform(this.remotes, new Function<Remote, String>() {

            @Override
            public String apply(Remote remote) {
                return remote.getName();
            }
        });
    }

    /**
     * @param remoteSupplier the remote repository to fetch from
     * @return {@code this}
     */
    public FetchOp addRemote(Supplier<Optional<Remote>> remoteSupplier) {
        Preconditions.checkNotNull(remoteSupplier);
        Optional<Remote> remote = remoteSupplier.get();
        Preconditions.checkState(remote.isPresent(), "Remote could not be resolved.");
        remotes.add(remote.get());

        return this;
    }

    public List<Remote> getRemotes() {
        return ImmutableList.copyOf(remotes);
    }

    /**
     * Executes the fetch operation.
     *
     * @return {@code null}
     * @see org.locationtech.geogig.api.AbstractGeoGigOp#call()
     */
    @Override
    protected TransferSummary _call() {
        if (all) {
            // Add all remotes to list.
            ImmutableList<Remote> localRemotes = command(RemoteListOp.class).call();
            for (Remote remote : localRemotes) {
                if (!remotes.contains(remote)) {
                    remotes.add(remote);
                }
            }
        } else if (remotes.size() == 0) {
            // If no remotes are specified, default to the origin remote
            addRemote("origin");
        }

        final ProgressListener progressListener = getProgressListener();
        progressListener.started();

        Optional<Integer> repoDepth = repository().getDepth();
        if (repoDepth.isPresent()) {
            if (fullDepth) {
                depth = Optional.of(Integer.MAX_VALUE);
            }
            if (depth.isPresent()) {
                if (depth.get() > repoDepth.get()) {
                    command(ConfigOp.class).setAction(ConfigAction.CONFIG_SET)
                            .setScope(ConfigScope.LOCAL).setName(Repository.DEPTH_CONFIG_KEY)
                            .setValue(depth.get().toString()).call();
                    repoDepth = depth;
                }
            }
        } else if (depth.isPresent() || fullDepth) {
            // Ignore depth, this is a full repository
            depth = Optional.absent();
            fullDepth = false;
        }

        TransferSummary result = new TransferSummary();

        for (Remote remote : remotes) {
            final ImmutableSet<Ref> remoteRemoteRefs = command(LsRemote.class)
                    .setRemote(Suppliers.ofInstance(Optional.of(remote)))
                    .retrieveTags(!remote.getMapped() && (!repoDepth.isPresent() || fullDepth))
                    .call();
            final ImmutableSet<Ref> localRemoteRefs = command(LsRemote.class)
                    .retrieveLocalRefs(true).setRemote(Suppliers.ofInstance(Optional.of(remote)))
                    .call();

            // If we have specified a depth to pull, we may have more history to pull from existing
            // refs.
            List<ChangedRef> needUpdate = findOutdatedRefs(remote, remoteRemoteRefs,
                    localRemoteRefs, depth);

            if (prune) {
                // Delete local refs that aren't in the remote
                List<Ref> locals = new ArrayList<Ref>();
                // only branches, not tags, appear in the remoteRemoteRefs list so we will not catch
                // any tags in this check. However, we do not track which remote originally
                // provided a tag so it makes sense not to prune them anyway.
                for (Ref remoteRef : remoteRemoteRefs) {
                    Optional<Ref> localRef = findLocal(remoteRef, localRemoteRefs);
                    if (localRef.isPresent()) {
                        locals.add(localRef.get());
                    }
                }
                for (Ref localRef : localRemoteRefs) {
                    if (!locals.contains(localRef)) {
                        // Delete the ref
                        ChangedRef changedRef = new ChangedRef(localRef, null,
                                ChangeTypes.REMOVED_REF);
                        needUpdate.add(changedRef);
                        command(UpdateRef.class).setDelete(true).setName(localRef.getName()).call();
                    }
                }
            }

            Optional<IRemoteRepo> remoteRepo = getRemoteRepo(remote, 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 {
                int refCount = 0;
                for (ChangedRef ref : needUpdate) {
                    if (ref.getType() != ChangeTypes.REMOVED_REF) {
                        refCount++;

                        Optional<Integer> newFetchLimit = depth;
                        // If we haven't specified a depth, but this is a shallow repository, set
                        // the
                        // fetch limit to the current repository depth.
                        if (!newFetchLimit.isPresent() && repoDepth.isPresent()
                                && ref.getType() == ChangeTypes.ADDED_REF) {
                            newFetchLimit = repoDepth;
                        }
                        // Fetch updated data from this ref
                        Ref newRef = ref.getNewRef();
                        remoteRepoInstance.fetchNewData(newRef, newFetchLimit, progressListener);

                        if (repoDepth.isPresent() && !fullDepth) {
                            // Update the repository depth if it is deeper than before.
                            int newDepth;
                            try {
                                newDepth = repository().graphDatabase().getDepth(
                                        newRef.getObjectId());
                            } catch (IllegalStateException e) {
                                throw new RuntimeException(ref.toString(), e);
                            }

                            if (newDepth > repoDepth.get()) {
                                command(ConfigOp.class).setAction(ConfigAction.CONFIG_SET)
                                        .setScope(ConfigScope.LOCAL)
                                        .setName(Repository.DEPTH_CONFIG_KEY)
                                        .setValue(Integer.toString(newDepth)).call();
                                repoDepth = Optional.of(newDepth);
                            }
                        }

                        // Update the ref
                        Ref updatedRef = updateLocalRef(newRef, remote, localRemoteRefs);
                        ref.setNewRef(updatedRef);
                    }
                }

                if (needUpdate.size() > 0) {
                    result.addAll(remote.getFetchURL(), needUpdate);
                }

                // Update HEAD ref
                if (!remote.getMapped()) {
                    Ref remoteHead = remoteRepoInstance.headRef();
                    if (remoteHead != null) {
                        updateLocalRef(remoteHead, remote, localRemoteRefs);
                    }
                }
            } finally {
                try {
                    remoteRepoInstance.close();
                } catch (IOException e) {
                    Throwables.propagate(e);
                }
            }
        }

        if (fullDepth) {
            // The full history was fetched, this is no longer a shallow clone
            command(ConfigOp.class).setAction(ConfigAction.CONFIG_UNSET)
                    .setScope(ConfigScope.LOCAL).setName(Repository.DEPTH_CONFIG_KEY).call();
        }

        progressListener.complete();

        return result;
    }

    /**
     * @param remote the remote to get
     * @return an interface for the remote repository
     */
    public Optional<IRemoteRepo> getRemoteRepo(Remote remote,
            DeduplicationService deduplicationService) {
        return RemoteUtils.newRemote(GlobalContextBuilder.builder.build(Hints.readOnly()), remote,
                repository(), deduplicationService);
    }

    private Ref updateLocalRef(Ref remoteRef, Remote remote, ImmutableSet<Ref> localRemoteRefs) {
        final String refName;
        if (remoteRef.getName().startsWith(Ref.TAGS_PREFIX)) {
            refName = remoteRef.getName();
        } else {
            refName = Ref.REMOTES_PREFIX + remote.getName() + "/" + remoteRef.localName();
        }
        Ref updatedRef = remoteRef;
        if (remoteRef instanceof SymRef) {
            String targetBranch = Ref.localName(((SymRef) remoteRef).getTarget());
            String newTarget = Ref.REMOTES_PREFIX + remote.getName() + "/" + targetBranch;
            command(UpdateSymRef.class).setName(refName).setNewValue(newTarget).call();
        } else {
            ObjectId effectiveId = remoteRef.getObjectId();

            if (remote.getMapped() && !repository().commitExists(remoteRef.getObjectId())) {
                effectiveId = graphDatabase().getMapping(effectiveId);
                updatedRef = new Ref(remoteRef.getName(), effectiveId);
            }
            command(UpdateRef.class).setName(refName).setNewValue(effectiveId).call();
        }
        return updatedRef;
    }

    /**
     * Filters the remote references for the given remote that are not present or outdated in the
     * local repository
     */
    private List<ChangedRef> findOutdatedRefs(Remote remote, ImmutableSet<Ref> remoteRefs,
            ImmutableSet<Ref> localRemoteRefs, Optional<Integer> depth) {

        List<ChangedRef> changedRefs = Lists.newLinkedList();

        for (Ref remoteRef : remoteRefs) {// refs/heads/xxx or refs/tags/yyy, though we don't handle
                                          // tags yet
            if (remote.getMapped()
                    && !remoteRef.localName().equals(Ref.localName(remote.getMappedBranch()))) {
                // for a mapped remote, we are only interested in the branch we are mapped to
                continue;
            }
            Optional<Ref> local = findLocal(remoteRef, localRemoteRefs);
            if (local.isPresent()) {
                if (!local.get().getObjectId().equals(remoteRef.getObjectId())) {
                    ChangedRef changedRef = new ChangedRef(local.get(), remoteRef,
                            ChangeTypes.CHANGED_REF);
                    changedRefs.add(changedRef);
                } else if (depth.isPresent()) {
                    int commitDepth = graphDatabase().getDepth(local.get().getObjectId());
                    if (depth.get() > commitDepth) {
                        ChangedRef changedRef = new ChangedRef(local.get(), remoteRef,
                                ChangeTypes.DEEPENED_REF);
                        changedRefs.add(changedRef);
                    }
                }
            } else {
                ChangedRef changedRef = new ChangedRef(null, remoteRef, ChangeTypes.ADDED_REF);
                changedRefs.add(changedRef);
            }
        }
        return changedRefs;
    }

    /**
     * Finds the corresponding local reference in {@code localRemoteRefs} for the given remote ref
     *
     * @param remoteRef a ref in the {@code refs/heads} or {@code refs/tags} namespace as given by
     *        {@link LsRemote} when querying a remote repository
     * @param localRemoteRefs the list of locally known references of the given remote in the
     *        {@code refs/remotes/<remote name>/} namespace
     */
    private Optional<Ref> findLocal(Ref remoteRef, ImmutableSet<Ref> localRemoteRefs) {
        if (remoteRef.getName().startsWith(Ref.TAGS_PREFIX)) {
            return command(RefParse.class).setName(remoteRef.getName()).call();
        } else {
            for (Ref localRef : localRemoteRefs) {
                if (localRef.localName().equals(remoteRef.localName())) {
                    return Optional.of(localRef);
                }
            }
            return Optional.absent();
        }
    }
}
TOP

Related Classes of org.locationtech.geogig.api.porcelain.FetchOp

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.