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

Java tutorial

Introduction

Here is the source code for org.locationtech.geogig.remotes.pack.PreparePackOp.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.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static org.locationtech.geogig.storage.BulkOpListener.NOOP_LISTENER;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.locationtech.geogig.model.DiffEntry;
import org.locationtech.geogig.model.ObjectId;
import org.locationtech.geogig.model.Ref;
import org.locationtech.geogig.model.RevCommit;
import org.locationtech.geogig.model.RevTag;
import org.locationtech.geogig.model.RevTree;
import org.locationtech.geogig.plumbing.FindChangedTrees;
import org.locationtech.geogig.porcelain.LogOp;
import org.locationtech.geogig.repository.AbstractGeoGigOp;
import org.locationtech.geogig.repository.IndexInfo;
import org.locationtech.geogig.repository.ProgressListener;
import org.locationtech.geogig.repository.Repository;
import org.locationtech.geogig.storage.IndexDatabase;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;

/**
 * Takes a {@link PackRequest} and a {@link PackBuilder} to be used to create a {@link Pack}.
 */
public class PreparePackOp extends AbstractGeoGigOp<Pack> {

    private PackRequest request;

    private PackBuilder builder;

    public PreparePackOp setRequest(PackRequest request) {
        this.request = request;
        return this;
    }

    public PackRequest getRequest() {
        return request;
    }

    public PreparePackOp setPackBuilder(PackBuilder builder) {
        this.builder = builder;
        return this;
    }

    public PackBuilder getBuilder() {
        return builder;
    }

    @Override
    protected Pack _call() {
        final PackRequest request = getRequest();
        final PackBuilder builder = getBuilder();
        checkState(request != null, "no request provided");
        checkState(builder != null, "no pack builder provided");

        final List<RefRequest> tagRequests = resolveTagRequests();
        final List<RefRequest> refs = resolveRefRequests();
        final Set<RevTag> tags = resolveWantTags(tagRequests);

        builder.start(tags);

        // commit ids of tips present on the remote end
        Set<ObjectId> have;
        // commit ids of tips not present on the remote end, and present on the local repo
        Set<ObjectId> want;

        boolean isTags = true;
        have = resolveHaveCommits(tagRequests, isTags);
        want = resolveWantCommits(tagRequests, isTags);

        isTags = false;
        have.addAll(resolveHaveCommits(refs, isTags));
        want.addAll(resolveWantCommits(refs, isTags));

        Iterable<RefRequest> allrequests = Iterables.concat(tagRequests, refs);
        processRequests(allrequests, want, have, builder);

        Pack pack = builder.build();
        return pack;
    }

    private Set<ObjectId> resolveWantCommits(List<RefRequest> refs, boolean isTags) {
        return resolveHeadCommits(refs, isTags, Predicates.alwaysTrue(), (o) -> o.want);
    }

    private Set<ObjectId> resolveHaveCommits(List<RefRequest> refs, boolean isTags) {
        return resolveHeadCommits(refs, isTags, (r) -> r.have.isPresent(), (o) -> o.have.get());
    }

    private Set<RevTag> resolveWantTags(List<RefRequest> tagRequests) {

        Iterable<ObjectId> ids = transform(tagRequests, (r) -> r.want);

        Iterator<RevTag> tags = objectDatabase().getAll(ids, NOOP_LISTENER, RevTag.class);

        return Sets.newHashSet(tags);
    }

    private Set<ObjectId> resolveHeadCommits(List<RefRequest> refs, boolean isTags,
            Predicate<? super RefRequest> filter, Function<? super RefRequest, ? extends ObjectId> function) {
        Iterable<ObjectId> ids = transform(filter(refs, filter), function);
        if (isTags) {
            Iterator<RevTag> tags = objectDatabase().getAll(ids, NOOP_LISTENER, RevTag.class);
            ids = newArrayList(Iterators.transform(tags, (t) -> t.getCommitId()));
        }
        return Sets.newHashSet(ids);
    }

    private List<RefRequest> resolveTagRequests() {
        final PackRequest req = this.request;

        List<RefRequest> refs;

        refs = newArrayList(filter(req.getRefs(), (r) -> r.name.startsWith(Ref.TAGS_PREFIX)));

        return refs;
    }

    private List<RefRequest> resolveRefRequests() {
        final PackRequest req = this.request;

        List<RefRequest> refs;

        refs = newArrayList(filter(req.getRefs(), (r) -> !r.name.startsWith(Ref.TAGS_PREFIX)));

        return refs;
    }

    private void processRequests(//
            Iterable<RefRequest> allrefs, //
            Set<ObjectId> want, //
            Set<ObjectId> have, //
            PackBuilder builder//
    ) {

        Repository local = repository();
        final ProgressListener progress = getProgressListener();
        java.util.function.Function<ProgressListener, String> oldIndicator = progress.progressIndicator();

        progress.setProgressIndicator(
                (p) -> String.format("Resolving missing commits... %,d", (int) p.getProgress()));
        progress.started();

        Set<RevCommit> visited = new HashSet<>();

        for (RefRequest req : allrefs) {
            builder.startRefResponse(req);
            final String refName = req.name;
            checkArgument(!req.want.isNull(), "Requested NULL tip for ref %s", refName);

            ObjectId wantCommit = req.want;
            ObjectId haveCommit = req.have.or(ObjectId.NULL);
            Iterator<RevCommit> branchCommits;
            if (wantCommit.equals(haveCommit)) {
                branchCommits = Collections.emptyIterator();
            } else {
                if (refName.startsWith(Ref.TAGS_PREFIX)) {
                    wantCommit = local.objectDatabase().getTag(wantCommit).getCommitId();
                    if (!haveCommit.isNull()) {
                        haveCommit = local.objectDatabase().getTag(haveCommit).getCommitId();
                    }
                }

                branchCommits = local.command(LogOp.class)//
                        .setTopoOrder(true)//
                        .setUntil(wantCommit)//
                        .setSince(haveCommit.isNull() ? null : haveCommit)//
                        .call();
            }
            int count = 0;
            while (branchCommits.hasNext()) {
                RevCommit commit = branchCommits.next();
                if (visited.add(commit)) {
                    builder.addCommit(commit);
                    if (request.isSyncIndexes()) {
                        addIndexes(builder, local, commit);
                    }
                    progress.setProgress(++count);
                }
            }

            builder.endRefResponse();
        }

        progress.complete();
        progress.setProgressIndicator(oldIndicator);
    }

    private Map<String, IndexInfo> indexInfosByFeatureTreeName;

    private void addIndexes(PackBuilder builder, Repository local, RevCommit commit) {
        if (indexInfosByFeatureTreeName != null && indexInfosByFeatureTreeName.isEmpty()) {
            return;
        }

        final IndexDatabase indexdb = local.indexDatabase();
        indexInfosByFeatureTreeName = indexdb.getIndexInfos().stream()
                .collect(Collectors.toMap(i -> i.getTreeName(), i -> i));
        if (indexInfosByFeatureTreeName.isEmpty()) {
            return;
        }

        final List<ObjectId> parents = commit.getParentIds().isEmpty() ? Collections.singletonList(ObjectId.NULL)
                : commit.getParentIds();

        for (ObjectId parentId : parents) {
            List<DiffEntry> changedTrees = local.command(FindChangedTrees.class).setOldTreeIsh(parentId)
                    .setNewTreeIsh(commit.getId()).call();

            for (DiffEntry treeDiff : changedTrees) {
                final String treePath = treeDiff.path();
                final IndexInfo indexInfo = indexInfosByFeatureTreeName.get(treePath);
                if (indexInfo == null) {
                    continue;
                }
                final ObjectId oldCanonical = treeDiff.oldObjectId().isNull() ? RevTree.EMPTY_TREE_ID
                        : treeDiff.oldObjectId();
                final ObjectId newCanonical = treeDiff.newObjectId().isNull() ? RevTree.EMPTY_TREE_ID
                        : treeDiff.newObjectId();

                ObjectId oldIndexTreeId = indexdb.resolveIndexedTree(indexInfo, oldCanonical)
                        .or(RevTree.EMPTY_TREE_ID);
                ObjectId newIndexTreeId = indexdb.resolveIndexedTree(indexInfo, newCanonical)
                        .or(RevTree.EMPTY_TREE_ID);

                builder.addIndex(indexInfo, newCanonical, oldIndexTreeId, newIndexTreeId);

            }
        }
    }
}