tikhomirov@525: /* tikhomirov@525: * Copyright (c) 2012-2013 TMate Software Ltd tikhomirov@525: * tikhomirov@525: * This program is free software; you can redistribute it and/or modify tikhomirov@525: * it under the terms of the GNU General Public License as published by tikhomirov@525: * the Free Software Foundation; version 2 of the License. tikhomirov@525: * tikhomirov@525: * This program is distributed in the hope that it will be useful, tikhomirov@525: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@525: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@525: * GNU General Public License for more details. tikhomirov@525: * tikhomirov@525: * For information on how to redistribute this software under tikhomirov@525: * the terms of a license other than GNU General Public License tikhomirov@525: * contact TMate Software at support@hg4j.com tikhomirov@525: */ tikhomirov@525: package org.tmatesoft.hg.internal; tikhomirov@525: tikhomirov@525: import java.io.IOException; tikhomirov@525: import java.nio.ByteBuffer; tikhomirov@525: import java.nio.channels.WritableByteChannel; tikhomirov@525: import java.util.ArrayList; tikhomirov@525: import java.util.List; tikhomirov@525: tikhomirov@525: import org.tmatesoft.hg.core.Nodeid; tikhomirov@525: import org.tmatesoft.hg.repo.HgDirstate; tikhomirov@525: import org.tmatesoft.hg.repo.HgManifest.Flags; tikhomirov@525: import org.tmatesoft.hg.util.Path; tikhomirov@525: tikhomirov@525: /** tikhomirov@525: * Facility to build a dirstate file as described in {@linkplain http://mercurial.selenic.com/wiki/DirState} tikhomirov@525: * tikhomirov@525: * @see http://mercurial.selenic.com/wiki/DirState tikhomirov@525: * @see HgDirstate tikhomirov@525: * @author Artem Tikhomirov tikhomirov@525: * @author TMate Software Ltd. tikhomirov@525: */ tikhomirov@525: public class DirstateBuilder { tikhomirov@525: private List normal = new ArrayList(); tikhomirov@525: private Nodeid parent1, parent2; tikhomirov@525: private final EncodingHelper encodingHelper; tikhomirov@525: tikhomirov@525: public DirstateBuilder(EncodingHelper encHelper) { tikhomirov@525: encodingHelper = encHelper; tikhomirov@525: } tikhomirov@525: tikhomirov@525: public void parents(Nodeid p1, Nodeid p2) { tikhomirov@525: parent1 = p1 == null ? Nodeid.NULL : p1; tikhomirov@525: parent2 = p2 == null ? Nodeid.NULL : p2; tikhomirov@525: } tikhomirov@525: tikhomirov@525: public void recordNormal(Path fname, Flags flags, int bytesWritten) { tikhomirov@525: // Mercurial seems to write "n 0 -1 unset fname" on `hg --clean co -rev ` tikhomirov@525: // and the reason for 'force lookup' I suspect is a slight chance of simultaneous modification tikhomirov@525: // of the file by user that doesn't alter its size the very second dirstate is being written tikhomirov@525: // (or the file is being updated and the update brought in changes that didn't alter the file size - tikhomirov@525: // with size and timestamp set, later `hg status` won't notice these changes) tikhomirov@525: tikhomirov@525: // However, as long as we use this class to write clean copies of the files, we can put all the fields tikhomirov@525: // right away. tikhomirov@525: int fmode = flags == Flags.RegularFile ? 0666 : 0777; // FIXME actual unix flags tikhomirov@525: int mtime = (int) (System.currentTimeMillis() / 1000); tikhomirov@525: normal.add(new HgDirstate.Record(fmode, bytesWritten, mtime,fname, null)); tikhomirov@525: tikhomirov@525: } tikhomirov@525: tikhomirov@525: public void serialize(WritableByteChannel dest) throws IOException { tikhomirov@525: assert parent1 != null : "Parent(s) of the working directory shall be set first"; tikhomirov@525: ByteBuffer bb = ByteBuffer.allocate(256); tikhomirov@525: bb.put(parent1.toByteArray()); tikhomirov@525: bb.put(parent2.toByteArray()); tikhomirov@525: bb.flip(); tikhomirov@525: // header tikhomirov@525: int written = dest.write(bb); tikhomirov@525: if (written != bb.limit()) { tikhomirov@525: throw new IOException("Incomplete write"); tikhomirov@525: } tikhomirov@525: bb.clear(); tikhomirov@525: // entries tikhomirov@525: for (HgDirstate.Record r : normal) { tikhomirov@525: // normal entry is 1+4+4+4+4+fname.length bytes tikhomirov@525: byte[] fname = encodingHelper.toDirstate(r.name().toString()); tikhomirov@525: bb = ensureCapacity(bb, 17 + fname.length); tikhomirov@525: bb.put((byte) 'n'); tikhomirov@525: bb.putInt(r.mode()); tikhomirov@525: bb.putInt(r.size()); tikhomirov@525: bb.putInt(r.modificationTime()); tikhomirov@525: bb.putInt(fname.length); tikhomirov@525: bb.put(fname); tikhomirov@525: bb.flip(); tikhomirov@525: written = dest.write(bb); tikhomirov@525: if (written != bb.limit()) { tikhomirov@525: throw new IOException("Incomplete write"); tikhomirov@525: } tikhomirov@525: bb.clear(); tikhomirov@525: } tikhomirov@525: } tikhomirov@525: tikhomirov@525: private static ByteBuffer ensureCapacity(ByteBuffer buf, int cap) { tikhomirov@525: if (buf.capacity() >= cap) { tikhomirov@525: return buf; tikhomirov@525: } tikhomirov@525: return ByteBuffer.allocate(cap); tikhomirov@525: } tikhomirov@525: }