org.locationtech.geogig.remotes.pack.DiffRemoteRefsOp.java Source code

Java tutorial

Introduction

Here is the source code for org.locationtech.geogig.remotes.pack.DiffRemoteRefsOp.java

Source

/* Copyright (c) 2017 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.remotes.pack;

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.locationtech.geogig.model.Ref;
import org.locationtech.geogig.model.RevTag;
import org.locationtech.geogig.plumbing.MapRef;
import org.locationtech.geogig.porcelain.TagListOp;
import org.locationtech.geogig.remotes.LsRemoteOp;
import org.locationtech.geogig.remotes.RefDiff;
import org.locationtech.geogig.remotes.internal.IRemoteRepo;
import org.locationtech.geogig.repository.AbstractGeoGigOp;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.MapDifference;
import com.google.common.collect.MapDifference.ValueDifference;
import com.google.common.collect.Maps;

/**
 * Compares the refs of a remote stored locally against the current state of the remote refs and
 * returns the differences.
 * <p>
 * By default the refs diffs are returned in the remote namespace (i.e.
 * {@code refs/remotes/<remote>/} namespace). This behavior can be controlled through
 * {@link #normalizeToLocalRefs()} and {@link #normalizeToRemoteRefs()}.
 */
public class DiffRemoteRefsOp extends AbstractGeoGigOp<List<RefDiff>> {

    private IRemoteRepo remote;

    private boolean formatAsRemoteRefs = true;

    private boolean getTags = false;

    @Override
    protected List<RefDiff> _call() {
        checkState(remote != null, "no remote provided");
        // list of refs/remotes/<remote>/<refname> or refs/heads according to formatAsRemoteRefs
        Map<String, Ref> remotes;
        Map<String, Ref> locals;
        {
            // current live remote refs in the remote's local namespace (e.g. refs/heads/<branch>)
            Iterable<Ref> remoteRefs = getRemoteRefs();
            if (formatAsRemoteRefs) {
                // format refs returned by the remote in its local namespaces to our repository's
                // remotes namespace
                remoteRefs = command(MapRef.class)//
                        .setRemote(remote.getInfo())//
                        .convertToRemote()//
                        .addAll(remoteRefs)//
                        .call();
            }
            // current local local copy of the remote refs (e.g. refs/remotes/<remote>/<branch>
            List<Ref> remoteLocalRefs = Lists.newArrayList(getRemoteLocalRefs());
            if (!formatAsRemoteRefs) {
                // format local repository copies of the remote refs to the remote's local namespace
                remoteLocalRefs = command(MapRef.class)//
                        .setRemote(remote.getInfo())//
                        .convertToLocal()//
                        .addAll(remoteLocalRefs)//
                        .call();
            }
            if (this.getTags) {
                Map<String, RevTag> tags = Maps.uniqueIndex(command(TagListOp.class).call(), (t) -> t.getName());
                for (Ref rf : remoteRefs) {
                    if (rf.getName().startsWith(Ref.TAGS_PREFIX) && tags.containsKey(rf.localName())) {
                        RevTag tag = tags.get(rf.localName());
                        remoteLocalRefs.add(new Ref(Ref.TAGS_PREFIX + tag.getName(), tag.getId()));
                    }
                }
            }
            remotes = Maps.uniqueIndex(remoteRefs, (r) -> r.getName());
            locals = Maps.uniqueIndex(remoteLocalRefs, (r) -> r.getName());
        }
        final boolean mapped = remote.getInfo().getMapped();
        if (mapped) {
            // for a mapped remote, we are only interested in the branch we are mapped to
            final String mappedBranch = remote.getInfo().getMappedBranch();
            checkNotNull(mappedBranch);
            final String mappedBranchName = Ref.localName(mappedBranch);
            remotes = Maps.filterKeys(remotes, (name) -> Ref.localName(name).equals(mappedBranchName));
            locals = Maps.filterKeys(locals, (name) -> Ref.localName(name).equals(mappedBranchName));
        }
        MapDifference<String, Ref> difference = Maps.difference(remotes, locals);

        // refs existing on the remote and not on the local repo
        Collection<Ref> newRemoteRefs = difference.entriesOnlyOnLeft().values();

        // remote refs existing on the local repo and not existing on the remote anymore
        Collection<Ref> removedRemoteRefs = difference.entriesOnlyOnRight().values();

        // refs existing both in local and remote with different objectIds
        Collection<ValueDifference<Ref>> changes = difference.entriesDiffering().values();

        List<RefDiff> diffs = new ArrayList<>();
        newRemoteRefs.forEach((r) -> diffs.add(RefDiff.added(r)));
        removedRemoteRefs.forEach((r) -> diffs.add(RefDiff.removed(r)));
        // v.leftValue() == new (remote copy), v.rightValue() == old (local copy)
        changes.forEach((v) -> diffs.add(RefDiff.updated(v.rightValue(), v.leftValue())));

        return diffs;
    }

    public DiffRemoteRefsOp setRemote(IRemoteRepo remote) {
        this.remote = remote;
        return this;
    }

    /**
     * If {@code true}, tags in the remote repo will be fetches and compared against local tags.
     * Defaults to {@code false}
     */
    public DiffRemoteRefsOp setGetRemoteTags(boolean getTags) {
        this.getTags = getTags;
        return this;
    }

    /**
     * Indicates to return branch and HEAD references as remote ones (i.e. in the
     * {@code refs/remotes/<remote>} namespace), tag references are always in {@code refs/tags} as
     * we don't track which remote each tag comes from. This is the default behavior.
     */
    public DiffRemoteRefsOp normalizeToRemoteRefs() {
        this.formatAsRemoteRefs = true;
        return this;
    }

    /**
     * Indicates to return branch refs as local refs (i.e. in the {@code refs/heads} namespace),
     * {@link Ref#HEAD} as a root ref; tag references are always in {@code refs/tags} as we don't
     * track which remote each tag comes from
     */
    public DiffRemoteRefsOp normalizeToLocalRefs() {
        this.formatAsRemoteRefs = false;
        return this;
    }

    private Set<Ref> getRemoteLocalRefs() {
        final ImmutableSet<Ref> localRemoteRefs;
        localRemoteRefs = command(LsRemoteOp.class)//
                .retrieveHead(true)//
                .retrieveLocalRefs(true)//
                .setRemote(remote)//
                .call();
        return localRemoteRefs;
    }

    private Set<Ref> getRemoteRefs() {
        final boolean getTags = this.getTags;

        Set<Ref> remoteRemoteRefs;
        remoteRemoteRefs = command(LsRemoteOp.class)//
                .setRemote(remote)//
                .retrieveHead(true)//
                .retrieveLocalRefs(false)//
                .retrieveTags(getTags)//
                .call();
        return remoteRemoteRefs;
    }
}