org.locationtech.geogig.plumbing.merge.ReportCommitConflictsOp.java Source code

Java tutorial

Introduction

Here is the source code for org.locationtech.geogig.plumbing.merge.ReportCommitConflictsOp.java

Source

/* Copyright (c) 2013-2016 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.plumbing.merge;

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

import org.locationtech.geogig.model.ObjectId;
import org.locationtech.geogig.model.Ref;
import org.locationtech.geogig.model.RevCommit;
import org.locationtech.geogig.model.RevFeature;
import org.locationtech.geogig.model.RevFeatureType;
import org.locationtech.geogig.model.RevObject;
import org.locationtech.geogig.model.RevObject.TYPE;
import org.locationtech.geogig.plumbing.DiffFeature;
import org.locationtech.geogig.plumbing.DiffTree;
import org.locationtech.geogig.plumbing.FindTreeChild;
import org.locationtech.geogig.plumbing.ResolveObjectType;
import org.locationtech.geogig.plumbing.RevObjectParse;
import org.locationtech.geogig.plumbing.diff.AttributeDiff;
import org.locationtech.geogig.plumbing.diff.FeatureDiff;
import org.locationtech.geogig.repository.AbstractGeoGigOp;
import org.locationtech.geogig.repository.AutoCloseableIterator;
import org.locationtech.geogig.repository.Conflict;
import org.locationtech.geogig.repository.DiffEntry;
import org.locationtech.geogig.repository.NodeRef;
import org.locationtech.geogig.repository.Repository;
import org.locationtech.geogig.repository.impl.DepthSearch;
import org.opengis.feature.type.PropertyDescriptor;

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

/**
 * Used for cherry pick and rebase to see if the changes from a single commit conflict with another
 * branch.
 * <p>
 * Reports conflicts between changes introduced by a given commit and the last commit of the current
 * head. That should give information about whether the specified commit can be applied safely on
 * the current branch without overwriting changes. It classifies the changes of the commit in
 * conflicting or unconflicting, so they can be applied partially
 */
public class ReportCommitConflictsOp extends AbstractGeoGigOp<MergeScenarioReport> {

    private RevCommit commit;

    private MergeScenarioConsumer consumer;

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

    public ReportCommitConflictsOp setConsumer(MergeScenarioConsumer consumer) {
        this.consumer = consumer;
        return this;
    }

    @Override
    protected MergeScenarioReport _call() {

        Preconditions.checkArgument(consumer != null, "No consumer provided.");

        MergeScenarioReport report = new MergeScenarioReport();

        ObjectId parentCommitId = ObjectId.NULL;
        if (commit.getParentIds().size() > 0) {
            parentCommitId = commit.getParentIds().get(0);
        }
        ObjectId parentTreeId = ObjectId.NULL;
        Repository repository = repository();
        if (repository.commitExists(parentCommitId)) {
            parentTreeId = repository.getCommit(parentCommitId).getTreeId();
        }
        // get changes
        try (AutoCloseableIterator<DiffEntry> diffs = command(DiffTree.class).setOldTree(parentTreeId)
                .setNewTree(commit.getTreeId()).setReportTrees(true).call()) {
            while (diffs.hasNext()) {
                DiffEntry diff = diffs.next();
                String path = diff.oldPath() == null ? diff.newPath() : diff.oldPath();
                Optional<RevObject> obj = command(RevObjectParse.class).setRefSpec(Ref.HEAD + ":" + path).call();
                switch (diff.changeType()) {
                case ADDED:
                    if (obj.isPresent()) {
                        TYPE type = command(ResolveObjectType.class).setObjectId(diff.getNewObject().getObjectId())
                                .call();
                        if (TYPE.TREE.equals(type)) {
                            NodeRef headVersion = command(FindTreeChild.class).setChildPath(path)
                                    .setParent(repository.getOrCreateHeadTree()).call().get();
                            if (!headVersion.getMetadataId().equals(diff.getNewObject().getMetadataId())) {
                                consumer.conflicted(new Conflict(path, ObjectId.NULL,
                                        diff.getNewObject().getMetadataId(), headVersion.getMetadataId()));
                                report.addConflict();
                            }
                        } else {
                            if (!obj.get().getId().equals(diff.newObjectId())) {
                                consumer.conflicted(
                                        new Conflict(path, ObjectId.NULL, diff.newObjectId(), obj.get().getId()));
                                report.addConflict();
                            }
                        }
                    } else {
                        consumer.unconflicted(diff);
                        report.addUnconflicted();
                    }
                    break;
                case REMOVED:
                    if (obj.isPresent()) {
                        if (obj.get().getId().equals(diff.oldObjectId())) {
                            consumer.unconflicted(diff);
                            report.addUnconflicted();
                        } else {
                            consumer.conflicted(
                                    new Conflict(path, diff.oldObjectId(), ObjectId.NULL, obj.get().getId()));
                            report.addConflict();
                        }
                    }
                    break;
                case MODIFIED:
                    TYPE type = command(ResolveObjectType.class).setObjectId(diff.getNewObject().getObjectId())
                            .call();
                    if (TYPE.TREE.equals(type)) {
                        // TODO:see how to do this. For now, we will pass any change as a conflicted
                        // one
                        if (!diff.isChange()) {
                            consumer.unconflicted(diff);
                            report.addUnconflicted();
                        }
                    } else {
                        String refSpec = Ref.HEAD + ":" + path;
                        obj = command(RevObjectParse.class).setRefSpec(refSpec).call();
                        if (!obj.isPresent()) {
                            // git reports this as a conflict but does not mark as conflicted, just
                            // adds
                            // the missing file.
                            // We add it and consider it unconflicted
                            consumer.unconflicted(diff);
                            report.addUnconflicted();
                            break;
                        }
                        RevFeature feature = (RevFeature) obj.get();
                        DepthSearch depthSearch = new DepthSearch(repository.objectDatabase());
                        Optional<NodeRef> noderef = depthSearch.find(this.workingTree().getTree(), path);
                        RevFeatureType featureType = command(RevObjectParse.class)
                                .setObjectId(noderef.get().getMetadataId()).call(RevFeatureType.class).get();
                        ImmutableList<PropertyDescriptor> descriptors = featureType.descriptors();
                        FeatureDiff featureDiff = command(DiffFeature.class)
                                .setOldVersion(Suppliers.ofInstance(diff.getOldObject()))
                                .setNewVersion(Suppliers.ofInstance(diff.getNewObject())).call();
                        Set<Entry<PropertyDescriptor, AttributeDiff>> attrDiffs = featureDiff.getDiffs().entrySet();
                        RevFeature newFeature = command(RevObjectParse.class).setObjectId(diff.newObjectId())
                                .call(RevFeature.class).get();
                        boolean ok = true;
                        for (Iterator<Entry<PropertyDescriptor, AttributeDiff>> iterator = attrDiffs
                                .iterator(); iterator.hasNext() && ok;) {
                            Entry<PropertyDescriptor, AttributeDiff> entry = iterator.next();
                            AttributeDiff attrDiff = entry.getValue();
                            PropertyDescriptor descriptor = entry.getKey();
                            switch (attrDiff.getType()) {
                            case ADDED:
                                if (descriptors.contains(descriptor)) {
                                    ok = false;
                                }
                                break;
                            case REMOVED:
                            case MODIFIED:
                                if (!descriptors.contains(descriptor)) {
                                    ok = false;
                                    break;
                                }
                                for (int i = 0; i < descriptors.size(); i++) {
                                    if (descriptors.get(i).equals(descriptor)) {
                                        Optional<Object> value = feature.get(i);
                                        Optional<Object> newValue = newFeature.get(i);
                                        if (!newValue.equals(value)) { // if it's going to end up
                                                                       // setting the same value, it
                                                                       // is
                                                                       // compatible, so no need to
                                                                       // check
                                            if (!attrDiff.canBeAppliedOn(value.orNull())) {
                                                ok = false;
                                            }
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        if (ok) {
                            consumer.unconflicted(diff);
                            report.addUnconflicted();
                        } else {
                            consumer.conflicted(
                                    new Conflict(path, diff.oldObjectId(), diff.newObjectId(), obj.get().getId()));
                            report.addConflict();
                        }
                    }

                    break;
                }

            }
        }
        consumer.finished();

        return report;

    }
}