/* 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.cli.porcelain; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.List; import jline.console.ConsoleReader; import org.locationtech.geogig.api.GeoGIG; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.RevCommit; import org.locationtech.geogig.api.RevObject; import org.locationtech.geogig.api.plumbing.CatObject; import org.locationtech.geogig.api.plumbing.FindCommonAncestor; import org.locationtech.geogig.api.plumbing.RefParse; import org.locationtech.geogig.api.plumbing.ResolveGeogigDir; import org.locationtech.geogig.api.plumbing.RevObjectParse; import org.locationtech.geogig.api.plumbing.diff.DiffEntry; import org.locationtech.geogig.api.plumbing.merge.Conflict; import org.locationtech.geogig.api.plumbing.merge.ConflictsReadOp; import org.locationtech.geogig.api.porcelain.FeatureNodeRefFromRefspec; import org.locationtech.geogig.api.porcelain.MergeOp; import org.locationtech.geogig.cli.AbstractCommand; import org.locationtech.geogig.cli.CLICommand; import org.locationtech.geogig.cli.GeogigCLI; import org.locationtech.geogig.cli.annotation.ObjectDatabaseReadOnly; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; import com.google.common.collect.Lists; import com.google.common.io.Files; /** * Show existing conflicts * * @see MergeOp */ // Currently it just print conflict descriptions, so they can be used by another tool instead. @ObjectDatabaseReadOnly @Parameters(commandNames = "conflicts", commandDescription = "Shows existing conflicts") public class Conflicts extends AbstractCommand implements CLICommand { @Parameter(description = "<path> [<path>...]") private List<String> paths = Lists.newArrayList(); @Parameter(names = { "--diff" }, description = "Show diffs instead of full element descriptions") private boolean previewDiff; @Parameter(names = { "--ids-only" }, description = "Just show ids of elements") private boolean idsOnly; @Parameter(names = { "--refspecs-only" }, description = "Just show refspecs of elements") private boolean refspecsOnly; private GeoGIG geogig; @Override public void runInternal(GeogigCLI cli) throws IOException { checkParameter(!(idsOnly && previewDiff), "Cannot use --diff and --ids-only at the same time"); checkParameter(!(refspecsOnly && previewDiff), "Cannot use --diff and --refspecs-only at the same time"); checkParameter(!(refspecsOnly && idsOnly), "Cannot use --ids-only and --refspecs-only at the same time"); geogig = cli.getGeogig(); List<Conflict> conflicts = geogig.command(ConflictsReadOp.class).call(); if (conflicts.isEmpty()) { cli.getConsole().println("No elements need merging."); return; } for (Conflict conflict : conflicts) { if (paths.isEmpty() || paths.contains(conflict.getPath())) { if (previewDiff) { printConflictDiff(conflict, cli.getConsole(), geogig); } else if (idsOnly) { cli.getConsole().println(conflict.toString()); } else if (refspecsOnly) { printRefspecs(conflict, cli.getConsole(), geogig); } else { printConflict(conflict, cli.getConsole(), geogig); } } } } private File getRebaseFolder() { URL dir = geogig.command(ResolveGeogigDir.class).call().get(); File rebaseFolder = new File(dir.getFile(), "rebase-apply"); return rebaseFolder; } private void printRefspecs(Conflict conflict, ConsoleReader console, GeoGIG geogig) throws IOException { ObjectId theirsHeadId; Optional<Ref> mergeHead = geogig.command(RefParse.class).setName(Ref.MERGE_HEAD).call(); if (mergeHead.isPresent()) { theirsHeadId = mergeHead.get().getObjectId(); } else { File branchFile = new File(getRebaseFolder(), "branch"); Preconditions .checkState(branchFile.exists(), "Cannot find merge/rebase head reference"); try { String currentBranch = Files.readFirstLine(branchFile, Charsets.UTF_8); Optional<Ref> rebaseHead = geogig.command(RefParse.class).setName(currentBranch) .call(); theirsHeadId = rebaseHead.get().getObjectId(); } catch (IOException e) { throw new IllegalStateException("Cannot read current branch info file"); } } Optional<RevCommit> theirsHead = geogig.command(RevObjectParse.class) .setObjectId(theirsHeadId).call(RevCommit.class); ObjectId oursHeadId = geogig.command(RefParse.class).setName(Ref.ORIG_HEAD).call().get() .getObjectId(); Optional<RevCommit> oursHead = geogig.command(RevObjectParse.class).setObjectId(oursHeadId) .call(RevCommit.class); Optional<ObjectId> commonAncestor = geogig.command(FindCommonAncestor.class) .setLeft(theirsHead.get()).setRight(oursHead.get()).call(); String ancestorPath = commonAncestor.get().toString() + ":" + conflict.getPath(); StringBuilder sb = new StringBuilder(); sb.append(conflict.getPath()); sb.append(" "); sb.append(ancestorPath); sb.append(" "); sb.append(oursHeadId.toString() + ":" + conflict.getPath()); sb.append(" "); sb.append(theirsHeadId.toString() + ":" + conflict.getPath()); console.println(sb.toString()); } private void printConflictDiff(Conflict conflict, ConsoleReader console, GeoGIG geogig) throws IOException { FullDiffPrinter diffPrinter = new FullDiffPrinter(false, true); console.println("---" + conflict.getPath() + "---"); ObjectId theirsHeadId; Optional<Ref> mergeHead = geogig.command(RefParse.class).setName(Ref.MERGE_HEAD).call(); if (mergeHead.isPresent()) { theirsHeadId = mergeHead.get().getObjectId(); } else { File branchFile = new File(getRebaseFolder(), "branch"); Preconditions .checkState(branchFile.exists(), "Cannot find merge/rebase head reference"); try { String currentBranch = Files.readFirstLine(branchFile, Charsets.UTF_8); Optional<Ref> rebaseHead = geogig.command(RefParse.class).setName(currentBranch) .call(); theirsHeadId = rebaseHead.get().getObjectId(); } catch (IOException e) { throw new IllegalStateException("Cannot read current branch info file"); } } Optional<RevCommit> theirsHead = geogig.command(RevObjectParse.class) .setObjectId(theirsHeadId).call(RevCommit.class); ObjectId oursHeadId = geogig.command(RefParse.class).setName(Ref.ORIG_HEAD).call().get() .getObjectId(); Optional<RevCommit> oursHead = geogig.command(RevObjectParse.class).setObjectId(oursHeadId) .call(RevCommit.class); Optional<ObjectId> commonAncestor = geogig.command(FindCommonAncestor.class) .setLeft(theirsHead.get()).setRight(oursHead.get()).call(); String ancestorPath = commonAncestor.get().toString() + ":" + conflict.getPath(); Optional<NodeRef> ancestorNodeRef = geogig.command(FeatureNodeRefFromRefspec.class) .setRefspec(ancestorPath).call(); String path = Ref.ORIG_HEAD + ":" + conflict.getPath(); Optional<NodeRef> oursNodeRef = geogig.command(FeatureNodeRefFromRefspec.class) .setRefspec(path).call(); DiffEntry diffEntry = new DiffEntry(ancestorNodeRef.orNull(), oursNodeRef.orNull()); console.println("Ours"); diffPrinter.print(geogig, console, diffEntry); path = theirsHeadId + ":" + conflict.getPath(); Optional<NodeRef> theirsNodeRef = geogig.command(FeatureNodeRefFromRefspec.class) .setRefspec(path).call(); diffEntry = new DiffEntry(ancestorNodeRef.orNull(), theirsNodeRef.orNull()); console.println("Theirs"); diffPrinter.print(geogig, console, diffEntry); } private void printConflict(Conflict conflict, ConsoleReader console, GeoGIG geogig) throws IOException { console.println(conflict.getPath()); console.println(); printObject("Ancestor", conflict.getAncestor(), console, geogig); console.println(); printObject("Ours", conflict.getOurs(), console, geogig); console.println(); printObject("Theirs", conflict.getTheirs(), console, geogig); console.println(); } private void printObject(String name, ObjectId id, ConsoleReader console, GeoGIG geogig) throws IOException { console.println(name + "\t" + id.toString()); if (!id.isNull()) { Optional<RevObject> obj = geogig.command(RevObjectParse.class).setObjectId(id).call(); CharSequence s = geogig.command(CatObject.class) .setObject(Suppliers.ofInstance(obj.get())).call(); console.println(s); } } }