tikhomirov@705: /* tikhomirov@705: * Copyright (c) 2013 TMate Software Ltd tikhomirov@705: * tikhomirov@705: * This program is free software; you can redistribute it and/or modify tikhomirov@705: * it under the terms of the GNU General Public License as published by tikhomirov@705: * the Free Software Foundation; version 2 of the License. tikhomirov@705: * tikhomirov@705: * This program is distributed in the hope that it will be useful, tikhomirov@705: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@705: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@705: * GNU General Public License for more details. tikhomirov@705: * tikhomirov@705: * For information on how to redistribute this software under tikhomirov@705: * the terms of a license other than GNU General Public License tikhomirov@705: * contact TMate Software at support@hg4j.com tikhomirov@705: */ tikhomirov@705: package org.tmatesoft.hg.internal; tikhomirov@705: tikhomirov@707: import java.io.File; tikhomirov@707: import java.io.FileOutputStream; tikhomirov@707: import java.io.IOException; tikhomirov@707: import java.io.OutputStream; tikhomirov@707: import java.nio.ByteBuffer; tikhomirov@707: import java.util.ArrayList; tikhomirov@707: import java.util.List; tikhomirov@707: tikhomirov@707: import org.tmatesoft.hg.core.HgFileRevision; tikhomirov@705: import org.tmatesoft.hg.core.HgIOException; tikhomirov@707: import org.tmatesoft.hg.core.Nodeid; tikhomirov@707: import org.tmatesoft.hg.repo.HgManifest; tikhomirov@705: import org.tmatesoft.hg.repo.HgMergeState; tikhomirov@707: import org.tmatesoft.hg.repo.HgRepositoryFiles; tikhomirov@707: import org.tmatesoft.hg.util.ByteChannel; tikhomirov@707: import org.tmatesoft.hg.util.CancelledException; tikhomirov@705: import org.tmatesoft.hg.util.Path; tikhomirov@707: import org.tmatesoft.hg.util.LogFacility.Severity; tikhomirov@705: tikhomirov@705: /** tikhomirov@705: * Constructs merge/state file tikhomirov@705: * tikhomirov@705: * @see HgMergeState tikhomirov@705: * @author Artem Tikhomirov tikhomirov@705: * @author TMate Software Ltd. tikhomirov@705: */ tikhomirov@705: public class MergeStateBuilder { tikhomirov@707: tikhomirov@705: private final Internals repo; tikhomirov@707: private final List unresolved = new ArrayList(); tikhomirov@707: private Nodeid stateParent = Nodeid.NULL; tikhomirov@705: tikhomirov@705: public MergeStateBuilder(Internals implRepo) { tikhomirov@705: repo = implRepo; tikhomirov@705: } tikhomirov@705: tikhomirov@707: public void prepare(Nodeid nodeid) { tikhomirov@707: assert nodeid != null; tikhomirov@707: unresolved.clear(); tikhomirov@707: stateParent = nodeid; tikhomirov@707: abandon(); tikhomirov@707: } tikhomirov@707: tikhomirov@705: public void resolved() { tikhomirov@705: throw Internals.notImplemented(); tikhomirov@705: } tikhomirov@705: tikhomirov@707: public void unresolved(Path file, HgFileRevision first, HgFileRevision second, HgFileRevision base, HgManifest.Flags flags) throws HgIOException { tikhomirov@707: Record r = new Record(file, first.getPath(), second.getPath(), base.getPath(), base.getRevision(), flags); tikhomirov@707: final File d = mergeStateDir(); tikhomirov@707: d.mkdirs(); tikhomirov@707: File f = new File(d, r.hash()); tikhomirov@707: try { tikhomirov@707: FileOutputStream fos = new FileOutputStream(f); tikhomirov@707: first.putContentTo(new OutputStreamSink(fos)); tikhomirov@707: fos.flush(); tikhomirov@707: fos.close(); tikhomirov@707: unresolved.add(r); tikhomirov@707: } catch (IOException ex) { tikhomirov@707: throw new HgIOException(String.format("Failed to write content of unresolved file %s to merge state at %s", file, f), f); tikhomirov@707: } catch (CancelledException ex) { tikhomirov@707: repo.getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation"); tikhomirov@707: } tikhomirov@705: } tikhomirov@705: tikhomirov@707: // merge/state serialization is not a part of a transaction tikhomirov@707: public void serialize() throws HgIOException { tikhomirov@707: if (unresolved.isEmpty()) { tikhomirov@707: return; tikhomirov@707: } tikhomirov@707: File mergeStateFile = repo.getRepositoryFile(HgRepositoryFiles.MergeState); tikhomirov@707: try { tikhomirov@707: final byte NL = '\n'; tikhomirov@707: FileOutputStream fos = new FileOutputStream(mergeStateFile); tikhomirov@707: fos.write(stateParent.toString().getBytes()); tikhomirov@707: fos.write(NL); tikhomirov@707: for(Record r : unresolved) { tikhomirov@707: fos.write(r.key.toString().getBytes()); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write('u'); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write(r.hash().toString().getBytes()); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write(r.fnameA.toString().getBytes()); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write(r.fnameAncestor.toString().getBytes()); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write(r.ancestorRev.toString().getBytes()); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write(r.fnameB.toString().getBytes()); tikhomirov@707: fos.write(0); tikhomirov@707: fos.write(r.flags.mercurialString().getBytes()); tikhomirov@707: fos.write(NL); tikhomirov@707: } tikhomirov@707: fos.flush(); tikhomirov@707: fos.close(); tikhomirov@707: } catch (IOException ex) { tikhomirov@707: throw new HgIOException("Failed to serialize merge state", mergeStateFile); tikhomirov@707: } tikhomirov@707: } tikhomirov@707: tikhomirov@707: public void abandon() { tikhomirov@707: File mergeStateDir = mergeStateDir(); tikhomirov@707: try { tikhomirov@707: FileUtils.rmdir(mergeStateDir); tikhomirov@707: } catch (IOException ex) { tikhomirov@707: // ignore almost silently tikhomirov@707: repo.getLog().dump(getClass(), Severity.Warn, ex, String.format("Failed to delete merge state in %s", mergeStateDir)); tikhomirov@707: } tikhomirov@707: } tikhomirov@707: tikhomirov@707: private File mergeStateDir() { tikhomirov@707: return repo.getRepositoryFile(HgRepositoryFiles.MergeState).getParentFile(); tikhomirov@707: } tikhomirov@707: tikhomirov@707: private static class Record { tikhomirov@707: public final Path key; tikhomirov@707: public final Path fnameA, fnameB, fnameAncestor; tikhomirov@707: public final Nodeid ancestorRev; tikhomirov@707: public final HgManifest.Flags flags; tikhomirov@707: private String hash; tikhomirov@707: tikhomirov@707: public Record(Path fname, Path a, Path b, Path ancestor, Nodeid rev, HgManifest.Flags f) { tikhomirov@707: key = fname; tikhomirov@707: fnameA = a; tikhomirov@707: fnameB = b; tikhomirov@707: fnameAncestor = ancestor; tikhomirov@707: ancestorRev = rev; tikhomirov@707: flags = f; tikhomirov@707: } tikhomirov@707: tikhomirov@707: public String hash() { tikhomirov@707: if (hash == null) { tikhomirov@707: hash = new DigestHelper().sha1(key).asHexString(); tikhomirov@707: } tikhomirov@707: return hash; tikhomirov@707: } tikhomirov@707: } tikhomirov@707: tikhomirov@707: private static class OutputStreamSink implements ByteChannel { tikhomirov@707: private final OutputStream out; tikhomirov@707: tikhomirov@707: public OutputStreamSink(OutputStream outputStream) { tikhomirov@707: out = outputStream; tikhomirov@707: } tikhomirov@707: tikhomirov@707: public int write(ByteBuffer buffer) throws IOException { tikhomirov@707: final int toWrite = buffer.remaining(); tikhomirov@707: if (toWrite <= 0) { tikhomirov@707: return 0; tikhomirov@707: } tikhomirov@707: if (buffer.hasArray()) { tikhomirov@707: out.write(buffer.array(), buffer.arrayOffset(), toWrite); tikhomirov@707: } else { tikhomirov@707: while (buffer.hasRemaining()) { tikhomirov@707: out.write(buffer.get()); tikhomirov@707: } tikhomirov@707: } tikhomirov@707: buffer.position(buffer.limit()); tikhomirov@707: return toWrite; tikhomirov@707: } tikhomirov@705: } tikhomirov@705: }