org.locationtech.geogig.api.plumbing.merge.CheckMergeScenarioOp.java Source code

Java tutorial

Introduction

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

Source

/* 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.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.plumbing.DiffTree;
import org.locationtech.geogig.api.plumbing.FindCommonAncestor;
import org.locationtech.geogig.api.plumbing.ResolveObjectType;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Checks for conflicts between changes introduced by different histories, or features that have to
 * be merged.
 * 
 * This operation analyzes a merge scenario and returns true if there are conflicts or some features
 * have to be merged. This last case happens when a feature has been edited by more than one branch,
 * and the changes introduced are not the same in all of them. This usually implies creating a
 * feature not already contained in the repo, but not necessarily.
 * 
 * This return value indicates an scenario where the merge operation has to be handled differently.
 * 
 * It returns false in case there are no such issues, and the branches to be merged are completely
 * independent in their edits.
 */
public class CheckMergeScenarioOp extends AbstractGeoGigOp<Boolean> {

    private List<RevCommit> commits;

    /**
     * @param commits the commits to check {@link RevCommit}
     */
    public CheckMergeScenarioOp setCommits(List<RevCommit> commits) {
        this.commits = commits;
        return this;
    }

    @Override
    protected Boolean _call() {
        if (commits.size() < 2) {
            return Boolean.FALSE;
        }
        Optional<ObjectId> ancestor = command(FindCommonAncestor.class).setLeft(commits.get(0))
                .setRight(commits.get(1)).call();
        Preconditions.checkState(ancestor.isPresent(), "No ancestor commit could be found.");
        for (int i = 2; i < commits.size(); i++) {
            ancestor = command(FindCommonAncestor.class).setLeft(commits.get(i)).setRightId(ancestor.get()).call();
            Preconditions.checkState(ancestor.isPresent(), "No ancestor commit could be found.");
        }

        Map<String, List<DiffEntry>> diffs = Maps.newHashMap();
        Set<String> removedPaths = Sets.newTreeSet();

        // we organize the changes made for each path
        for (RevCommit commit : commits) {
            Iterator<DiffEntry> toMergeDiffs = command(DiffTree.class).setReportTrees(true)
                    .setOldTree(ancestor.get()).setNewTree(commit.getId()).call();
            while (toMergeDiffs.hasNext()) {
                DiffEntry diff = toMergeDiffs.next();
                String path = diff.oldPath() == null ? diff.newPath() : diff.oldPath();
                if (diffs.containsKey(path)) {
                    diffs.get(path).add(diff);
                } else {
                    diffs.put(path, Lists.newArrayList(diff));
                }
                if (ChangeType.REMOVED.equals(diff.changeType())) {
                    removedPaths.add(path);
                }
            }
        }

        // now we check that, for any path, changes are compatible
        Collection<List<DiffEntry>> values = diffs.values();
        for (List<DiffEntry> list : values) {
            for (int i = 0; i < list.size(); i++) {
                for (int j = i + 1; j < list.size(); j++) {
                    if (hasConflicts(list.get(i), list.get(j))) {
                        return true;
                    }
                }
                if (!ChangeType.REMOVED.equals(list.get(i).changeType())) {
                    if (removedPaths.contains(list.get(i).getNewObject().getParentPath())) {
                        return true;
                    }
                }
            }
        }

        return false;

    }

    private boolean hasConflicts(DiffEntry diff, DiffEntry diff2) {
        if (!diff.changeType().equals(diff2.changeType())) {
            return true;
        }
        switch (diff.changeType()) {
        case ADDED:
            TYPE type = command(ResolveObjectType.class).setObjectId(diff.getNewObject().objectId()).call();
            if (TYPE.TREE.equals(type)) {
                return !diff.getNewObject().getMetadataId().equals(diff2.getNewObject().getMetadataId());
            }
            return !diff.getNewObject().objectId().equals(diff2.getNewObject().objectId());
        case REMOVED:
            break;
        case MODIFIED:
            type = command(ResolveObjectType.class).setObjectId(diff.getNewObject().objectId()).call();
            if (TYPE.TREE.equals(type)) {
                return !diff.getNewObject().getMetadataId().equals(diff2.getNewObject().getMetadataId());
            } else {
                return !diff.newObjectId().equals(diff2.newObjectId());

            }
        }
        return false;
    }
}