Package org.locationtech.geogig.api.plumbing.merge

Source Code of org.locationtech.geogig.api.plumbing.merge.ReportMergeScenarioOp

/* 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:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing.merge;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.FeatureInfo;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureBuilder;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.plumbing.DiffFeature;
import org.locationtech.geogig.api.plumbing.DiffTree;
import org.locationtech.geogig.api.plumbing.FindCommonAncestor;
import org.locationtech.geogig.api.plumbing.FindTreeChild;
import org.locationtech.geogig.api.plumbing.ResolveObjectType;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.RevParse;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;
import org.locationtech.geogig.api.plumbing.diff.FeatureDiff;
import org.opengis.feature.Feature;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.Maps;

/**
* Reports conflicts between changes introduced by two different histories. Given a commit and
* another reference commit, it returns the set of changes from the common ancestor to the first
* commit, classified according to whether they can or not be safely applied onto the reference
* commit. Changes that will have no effect on the target commit are not included as unconflicted.
*/
public class ReportMergeScenarioOp extends AbstractGeoGigOp<MergeScenarioReport> {

    private RevCommit toMerge;

    private RevCommit mergeInto;

    /**
     * @param toMerge the commit with the changes to apply {@link RevCommit}
     */
    public ReportMergeScenarioOp setToMergeCommit(RevCommit toMerge) {
        this.toMerge = toMerge;
        return this;
    }

    /**
     * @param mergeInto the commit into which changes are to be merged {@link RevCommit}
     */
    public ReportMergeScenarioOp setMergeIntoCommit(RevCommit mergeInto) {
        this.mergeInto = mergeInto;
        return this;
    }

    @Override
    protected MergeScenarioReport _call() {

        Optional<ObjectId> ancestor = command(FindCommonAncestor.class).setLeft(toMerge)
                .setRight(mergeInto).call();
        Preconditions.checkState(ancestor.isPresent(), "No ancestor commit could be found.");

        Map<String, DiffEntry> mergeIntoDiffs = Maps.newHashMap();
        MergeScenarioReport report = new MergeScenarioReport();

        Iterator<DiffEntry> diffs = command(DiffTree.class).setOldTree(ancestor.get())
                .setReportTrees(true).setNewTree(mergeInto.getId()).call();
        while (diffs.hasNext()) {
            DiffEntry diff = diffs.next();
            String path = diff.oldPath() == null ? diff.newPath() : diff.oldPath();
            mergeIntoDiffs.put(path, diff);
        }

        Iterator<DiffEntry> toMergeDiffs = command(DiffTree.class).setOldTree(ancestor.get())
                .setReportTrees(true).setNewTree(toMerge.getId()).call();
        while (toMergeDiffs.hasNext()) {
            DiffEntry toMergeDiff = toMergeDiffs.next();
            String path = toMergeDiff.oldPath() == null ? toMergeDiff.newPath() : toMergeDiff
                    .oldPath();
            if (mergeIntoDiffs.containsKey(path)) {
                RevCommit ancestorCommit = command(RevObjectParse.class)
                        .setRefSpec(ancestor.get().toString()).call(RevCommit.class).get();
                RevTree ancestorTree = command(RevObjectParse.class)
                        .setObjectId(ancestorCommit.getTreeId()).call(RevTree.class).get();
                Optional<NodeRef> ancestorVersion = command(FindTreeChild.class).setChildPath(path)
                        .setParent(ancestorTree).call();
                ObjectId ancestorVersionId = ancestorVersion.isPresent() ? ancestorVersion.get()
                        .getNode().getObjectId() : ObjectId.NULL;
                ObjectId theirs = toMergeDiff.getNewObject() == null ? ObjectId.NULL : toMergeDiff
                        .getNewObject().objectId();
                DiffEntry mergeIntoDiff = mergeIntoDiffs.get(path);
                ObjectId ours = mergeIntoDiff.getNewObject() == null ? ObjectId.NULL
                        : mergeIntoDiff.getNewObject().objectId();
                if (!mergeIntoDiff.changeType().equals(toMergeDiff.changeType())) {
                    report.addConflict(new Conflict(path, ancestorVersionId, ours, theirs));
                    continue;
                }
                switch (toMergeDiff.changeType()) {
                case ADDED:
                    if (toMergeDiff.getNewObject().equals(mergeIntoDiff.getNewObject())) {
                        // already added in current branch, no need to do anything
                    } else {
                        TYPE type = command(ResolveObjectType.class).setObjectId(
                                toMergeDiff.getNewObject().objectId()).call();
                        if (TYPE.TREE.equals(type)) {
                            boolean conflict = !toMergeDiff.getNewObject().getMetadataId()
                                    .equals(mergeIntoDiff.getNewObject().getMetadataId());
                            if (conflict) {
                                // In this case, we store the metadata id, not the element id
                                ancestorVersionId = ancestorVersion.isPresent() ? ancestorVersion
                                        .get().getMetadataId() : ObjectId.NULL;
                                ours = mergeIntoDiff.getNewObject().getMetadataId();
                                theirs = toMergeDiff.getNewObject().getMetadataId();
                                report.addConflict(new Conflict(path, ancestorVersionId, ours,
                                        theirs));
                            }
                            // if the metadata ids match, it means both branches have added the same
                            // tree, maybe with different content, but there is no need to do
                            // anything. The correct tree is already there and the merge can be run
                            // safely, so we do not add it neither as a conflicted change nor as an
                            // unconflicted one
                        } else {
                            report.addConflict(new Conflict(path, ancestorVersionId, ours, theirs));
                        }
                    }
                    break;
                case REMOVED:
                    // removed by both histories => no conflict and no need to do anything
                    break;
                case MODIFIED:
                    TYPE type = command(ResolveObjectType.class).setObjectId(
                            toMergeDiff.getNewObject().objectId()).call();
                    if (TYPE.TREE.equals(type)) {
                        boolean conflict = !toMergeDiff.getNewObject().getMetadataId()
                                .equals(mergeIntoDiff.getNewObject().getMetadataId());
                        if (conflict) {
                            // In this case, we store the metadata id, not the element id
                            ancestorVersionId = ancestorVersion.isPresent() ? ancestorVersion.get()
                                    .getMetadataId() : ObjectId.NULL;
                            ours = mergeIntoDiff.getNewObject().getMetadataId();
                            theirs = toMergeDiff.getNewObject().getMetadataId();
                            report.addConflict(new Conflict(path, ancestorVersionId, ours, theirs));
                        }
                    } else {
                        FeatureDiff toMergeFeatureDiff = command(DiffFeature.class)
                                .setOldVersion(Suppliers.ofInstance(toMergeDiff.getOldObject()))
                                .setNewVersion(Suppliers.ofInstance(toMergeDiff.getNewObject()))
                                .call();
                        FeatureDiff mergeIntoFeatureDiff = command(DiffFeature.class)
                                .setOldVersion(Suppliers.ofInstance(mergeIntoDiff.getOldObject()))
                                .setNewVersion(Suppliers.ofInstance(mergeIntoDiff.getNewObject()))
                                .call();
                        if (toMergeFeatureDiff.conflicts(mergeIntoFeatureDiff)) {
                            report.addConflict(new Conflict(path, ancestorVersionId, ours, theirs));
                        } else {
                            // if the feature types are different we report a conflict and do not
                            // try to perform automerge
                            if (!toMergeDiff.getNewObject().getMetadataId()
                                    .equals(mergeIntoDiff.getNewObject().getMetadataId())) {
                                report.addConflict(new Conflict(path, ancestorVersionId, ours,
                                        theirs));
                            } else if (!toMergeFeatureDiff.equals(mergeIntoFeatureDiff)) {
                                Feature mergedFeature = command(MergeFeaturesOp.class)
                                        .setFirstFeature(mergeIntoDiff.getNewObject())
                                        .setSecondFeature(toMergeDiff.getNewObject())
                                        .setAncestorFeature(mergeIntoDiff.getOldObject()).call();
                                RevFeature revFeature = RevFeatureBuilder.build(mergedFeature);
                                if (revFeature.getId().equals(toMergeDiff.newObjectId())) {
                                    // the resulting merged feature equals the feature to merge from
                                    // the branch, which means that it exists in the repo and there
                                    // is no need to add it
                                    report.addUnconflicted(toMergeDiff);
                                } else {
                                    RevFeatureType featureType = command(RevObjectParse.class)
                                            .setObjectId(
                                                    mergeIntoDiff.getNewObject().getMetadataId())
                                            .call(RevFeatureType.class).get();
                                    FeatureInfo merged = new FeatureInfo(mergedFeature,
                                            featureType, path);
                                    report.addMerged(merged);
                                }
                            }
                        }
                    }
                    break;
                }
            } else {
                // If the element is a tree, not a feature, it might be a conflict even if the other
                // branch has not modified it.
                // If we are removing the tree, we have to make sure that there are no features
                // modified in the other branch under it.
                if (ChangeType.REMOVED.equals(toMergeDiff.changeType())) {
                    TYPE type = command(ResolveObjectType.class).setObjectId(
                            toMergeDiff.oldObjectId()).call();
                    if (TYPE.TREE.equals(type)) {
                        String parentPath = toMergeDiff.oldPath();
                        Set<Entry<String, DiffEntry>> entries = mergeIntoDiffs.entrySet();
                        boolean conflict = false;
                        for (Entry<String, DiffEntry> entry : entries) {
                            if (entry.getKey().startsWith(parentPath)) {
                                if (!ChangeType.REMOVED.equals(entry.getValue().changeType())) {
                                    RevCommit ancestorCommit = command(RevObjectParse.class)
                                            .setRefSpec(ancestor.get().toString())
                                            .call(RevCommit.class).get();
                                    RevTree ancestorTree = command(RevObjectParse.class)
                                            .setObjectId(ancestorCommit.getTreeId())
                                            .call(RevTree.class).get();
                                    Optional<NodeRef> ancestorVersion = command(FindTreeChild.class)
                                            .setChildPath(path).setParent(ancestorTree).call();
                                    ObjectId ancestorVersionId = ancestorVersion.isPresent() ? ancestorVersion
                                            .get().getNode().getObjectId()
                                            : ObjectId.NULL;
                                    ObjectId theirs = toMergeDiff.getNewObject() == null ? ObjectId.NULL
                                            : toMergeDiff.getNewObject().objectId();
                                    String oursRefSpec = mergeInto.getId().toString() + ":"
                                            + parentPath;
                                    Optional<ObjectId> ours = command(RevParse.class).setRefSpec(
                                            oursRefSpec).call();
                                    report.addConflict(new Conflict(path, ancestorVersionId, ours
                                            .get(), theirs));
                                    conflict = true;
                                    break;
                                }
                            }
                        }
                        if (!conflict) {
                            report.addUnconflicted(toMergeDiff);
                        }
                    } else {
                        report.addUnconflicted(toMergeDiff);
                    }
                } else {
                    report.addUnconflicted(toMergeDiff);
                }
            }

        }

        return report;

    }
}
TOP

Related Classes of org.locationtech.geogig.api.plumbing.merge.ReportMergeScenarioOp

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.