package com.aragost.javahg.merge;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.aragost.javahg.Changeset;
import com.aragost.javahg.Repository;
import com.aragost.javahg.WorkingCopy;
import com.aragost.javahg.commands.LogCommand;
import com.aragost.javahg.commands.ResolveCommand;
import com.aragost.javahg.commands.ResolveStatusLine;
import com.aragost.javahg.internals.AbstractCommand;
import com.aragost.javahg.internals.HgInputStream;
import com.aragost.javahg.internals.UnexpectedCommandOutputException;
import com.aragost.javahg.internals.Utils;
import com.google.common.collect.Lists;
/**
* Base class for a context where conflicts are resolved.
* <p>
* That is for example after a merge or graft.
*/
public abstract class ConflictResolvingContext {
private static final byte[] USE_RESOLVE_EXPLANATION = "use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon\n".getBytes();
private static final byte[] USE_RESOLVE_SHORT_EXPLANATION = "use 'hg resolve' to retry unresolved file merges\n".getBytes();
private static final byte[] BRANCH_MERGE_EXPLANATION = "(branch merge, don't forget to commit)\n".getBytes();
private static final byte[] FILES_REMOVED = " files removed, ".getBytes();
private static final byte[] FILES_MERGED = " files merged, ".getBytes();
private static final byte[] FILES_UPDATED = " files updated, ".getBytes();
private static final byte[] LOCAL_CHANGED_AFTER = " which remote deleted\nuse (c)hanged version or (d)elete? c\n".getBytes();
private static final byte[] LOCAL_CHANGED = "local changed ".getBytes();
private static final byte[] REMOVED_CHANGE_AFTER = " which local deleted\nuse (c)hanged version or leave (d)eleted? c\n".getBytes();
private static final byte[] REMOTE_CHANGED = "remote changed ".getBytes();
private static final byte[] CONFLICTING_FLAGS = "conflicting flags for ".getBytes();
private static final String NONE_EXEC_SYMLINK = "(n)one, e(x)ec or sym(l)ink? n\n";
private static final byte[] LARGEFILES_CHANGED = "getting changed largefiles".getBytes();
private static final byte[] LARGEFILES_UPDATED = " largefiles updated, ".getBytes();
private static final byte[] CHANGESET = "changeset ".getBytes();
private AbstractCommand command;
private WorkingCopy workingCopy;
private List<KeepDeleteConflict> keepDeleteConflicts = Lists.newArrayList();
private List<FlagConflict> flagConflicts = Lists.newArrayList();
private List<MergeConflict> mergeConflicts;
private Changeset base;
public ConflictResolvingContext(AbstractCommand command) {
this.command = command;
this.workingCopy = this.command.getRepository().workingCopy();
}
public WorkingCopy getWorkingCopy() {
return workingCopy;
}
public Collection<FlagConflict> getFlagConflicts() {
return flagConflicts;
}
public List<KeepDeleteConflict> getKeepDeleteConflicts() {
return keepDeleteConflicts;
}
public synchronized List<MergeConflict> getMergeConflicts() {
if (this.mergeConflicts == null) {
List<ResolveStatusLine> lines = ResolveCommand.on(getRepository()).list();
List<MergeConflict> conflicts = Lists.newArrayListWithCapacity(lines.size());
for (ResolveStatusLine line : lines) {
MergeConflict mergeConflict = new MergeConflict(this, line.getFileName());
conflicts.add(mergeConflict);
}
this.mergeConflicts = conflicts;
}
return mergeConflicts;
}
public Repository getRepository() {
return this.command.getRepository();
}
public AbstractCommand getCommand() {
return command;
}
/**
* Read input from the stream record conflicts.
* <p>
*
* @param in
* @param whenUnknowReturn
* @throws IOException
*/
public void processStream(HgInputStream in, boolean whenUnknowReturn) throws IOException {
while (!in.isEof()) {
byte[] endPattern = null;
KeepDeleteConflict.State keepState = null;
in.match(' ');
if (in.match(REMOTE_CHANGED)) {
endPattern = REMOVED_CHANGE_AFTER;
keepState = KeepDeleteConflict.State.DELETE;
} else if (in.match(LOCAL_CHANGED)) {
endPattern = LOCAL_CHANGED_AFTER;
keepState = KeepDeleteConflict.State.KEEP;
} else if (in.match(CONFLICTING_FLAGS)) {
String fileName = in.textUpTo('\n');
in.mustMatch(NONE_EXEC_SYMLINK.getBytes());
FlagConflict flagConflict = new FlagConflict(this, fileName);
this.flagConflicts.add(flagConflict);
} else if ((in.readDecimal()) != null) {
in.mustMatch(FILES_UPDATED);
Integer merged = in.readDecimal();
in.mustMatch(FILES_MERGED);
Integer removed = in.readDecimal();
in.mustMatch(FILES_REMOVED);
Integer unresolved = in.readDecimal();
in.mustMatch(" files unresolved\n".getBytes());
if (merged == null || removed == null || unresolved == null) {
throw new UnexpectedCommandOutputException(this.command, null);
}
if (unresolved.intValue() == 0) {
this.mergeConflicts = Collections.emptyList();
// If there is merge conflicts it will be lazy
// intialized
}
} else if (in.match(USE_RESOLVE_EXPLANATION)) {
// ignore
} else if (in.match(USE_RESOLVE_SHORT_EXPLANATION)) {
// ignore
} else if (in.match(BRANCH_MERGE_EXPLANATION)) {
// ignore
} else if (in.match(LARGEFILES_CHANGED)) {
// ignore
} else if (in.match(CHANGESET)) {
Utils.consumeAll(in);
} else if (in.find(LARGEFILES_UPDATED)) {
in.upTo('\n'); // ignore largefiles statistics line. e.g. 2 largefiles updated, 0 removed
} else {
if (whenUnknowReturn) {
return;
} else {
throw new UnexpectedCommandOutputException(Utils.readStream(in,
command.getRepository().newDecoder()));
}
}
if (endPattern != null) {
String fileName = in.textUpTo(endPattern);
KeepDeleteConflict keepRemoveConflict = new KeepDeleteConflict(this, fileName, keepState);
this.keepDeleteConflicts.add(keepRemoveConflict);
}
}
}
public Changeset getLocal() {
return getWorkingCopy().getParent1();
}
public Changeset getRemote() {
return getWorkingCopy().getParent2();
}
public synchronized Changeset getBase() {
if (base == null) {
String ancestorRev = "ancestor(" + getLocal().getNode() + "," + getRemote().getNode() + ")";
this.base = LogCommand.on(getRepository()).rev(ancestorRev).single();
}
return this.base;
}
}