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@526: import java.io.File; tikhomirov@526: import java.io.FileOutputStream; tikhomirov@525: import java.io.IOException; tikhomirov@525: import java.nio.ByteBuffer; tikhomirov@526: import java.nio.channels.FileChannel; tikhomirov@525: import java.nio.channels.WritableByteChannel; tikhomirov@526: import java.util.Map; tikhomirov@526: import java.util.TreeMap; tikhomirov@525: tikhomirov@526: import org.tmatesoft.hg.core.HgIOException; tikhomirov@525: import org.tmatesoft.hg.core.Nodeid; tikhomirov@525: import org.tmatesoft.hg.repo.HgDirstate; tikhomirov@526: import org.tmatesoft.hg.repo.HgDirstate.EntryKind; tikhomirov@526: import org.tmatesoft.hg.repo.HgDirstate.Record; tikhomirov@526: import org.tmatesoft.hg.repo.HgInvalidStateException; tikhomirov@525: import org.tmatesoft.hg.repo.HgManifest.Flags; tikhomirov@526: import org.tmatesoft.hg.repo.HgRepositoryFiles; 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@526: private Map normal = new TreeMap(); tikhomirov@526: private Map added = new TreeMap(); tikhomirov@526: private Map removed = new TreeMap(); tikhomirov@526: private Map merged = new TreeMap(); tikhomirov@525: private Nodeid parent1, parent2; tikhomirov@526: private final Internals hgRepo; tikhomirov@525: private final EncodingHelper encodingHelper; tikhomirov@525: tikhomirov@526: public DirstateBuilder(Internals internalRepo) { tikhomirov@526: hgRepo = internalRepo; tikhomirov@526: encodingHelper = internalRepo.buildFileNameEncodingHelper(); 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@526: forget(fname); tikhomirov@526: normal.put(fname, new HgDirstate.Record(fmode, bytesWritten, mtime, fname, null)); tikhomirov@526: } tikhomirov@526: tikhomirov@526: public void recordUncertain(Path fname) { tikhomirov@526: // `hg revert` puts "n 0 -1 unset" for the reverted file, so shall we tikhomirov@526: forget(fname); tikhomirov@526: normal.put(fname, new HgDirstate.Record(0, -1, -1, fname, null)); tikhomirov@526: } tikhomirov@526: tikhomirov@526: private void forget(Path fname) { tikhomirov@526: normal.remove(fname); tikhomirov@526: added.remove(fname); tikhomirov@526: removed.remove(fname); tikhomirov@526: merged.remove(fname); 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@526: @SuppressWarnings("unchecked") tikhomirov@526: Map[] all = new Map[] {normal, added, removed, merged}; tikhomirov@526: for (Map m : all) { tikhomirov@526: for (HgDirstate.Record r : m.values()) { tikhomirov@526: // regular entry is 1+4+4+4+4+fname.length bytes tikhomirov@526: // it might get extended with copy origin, prepended with 0 byte tikhomirov@526: byte[] fname = encodingHelper.toDirstate(r.name()); tikhomirov@526: byte[] copyOrigin = r.copySource() == null ? null : encodingHelper.toDirstate(r.copySource()); tikhomirov@526: int length = fname.length + (copyOrigin == null ? 0 : (1 + copyOrigin.length)); tikhomirov@526: bb = ensureCapacity(bb, 17 + length); tikhomirov@526: bb.put((byte) 'n'); tikhomirov@526: bb.putInt(r.mode()); tikhomirov@526: bb.putInt(r.size()); tikhomirov@526: bb.putInt(r.modificationTime()); tikhomirov@526: bb.putInt(length); tikhomirov@526: bb.put(fname); tikhomirov@526: if (copyOrigin != null) { tikhomirov@526: bb.put((byte) 0); tikhomirov@526: bb.put(copyOrigin); tikhomirov@526: } tikhomirov@526: bb.flip(); tikhomirov@526: written = dest.write(bb); tikhomirov@526: if (written != bb.limit()) { tikhomirov@526: throw new IOException("Incomplete write"); tikhomirov@526: } tikhomirov@526: bb.clear(); tikhomirov@525: } tikhomirov@525: } tikhomirov@525: } tikhomirov@526: tikhomirov@526: public void serialize() throws HgIOException { tikhomirov@526: File dirstateFile = hgRepo.getRepositoryFile(HgRepositoryFiles.Dirstate); tikhomirov@526: try { tikhomirov@526: FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel(); tikhomirov@526: serialize(dirstate); tikhomirov@526: dirstate.close(); tikhomirov@526: } catch (IOException ex) { tikhomirov@526: throw new HgIOException("Can't write down new directory state", ex, dirstateFile); tikhomirov@526: } tikhomirov@526: } tikhomirov@526: tikhomirov@526: public void fillFrom(DirstateReader dirstate) { tikhomirov@526: // TODO preserve order, if reasonable and possible tikhomirov@526: dirstate.readInto(new HgDirstate.Inspector() { tikhomirov@526: tikhomirov@526: public boolean next(EntryKind kind, Record entry) { tikhomirov@526: switch (kind) { tikhomirov@526: case Normal: normal.put(entry.name(), entry); break; tikhomirov@526: case Added : added.put(entry.name(), entry); break; tikhomirov@526: case Removed : removed.put(entry.name(), entry); break; tikhomirov@526: case Merged : merged.put(entry.name(), entry); break; tikhomirov@526: default: throw new HgInvalidStateException(String.format("Unexpected entry in the dirstate: %s", kind)); tikhomirov@526: } tikhomirov@526: return true; tikhomirov@526: } tikhomirov@526: }); tikhomirov@526: parents(dirstate.parents().first(), dirstate.parents().second()); tikhomirov@526: } tikhomirov@526: 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: }