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@580: public void recordNormal(Path fname, int fmode, int mtime, int bytesWritten) { 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@529: public void recordAdded(Path fname, Flags flags, int size) { tikhomirov@529: forget(fname); tikhomirov@529: added.put(fname, new HgDirstate.Record(0, -1, -1, fname, null)); tikhomirov@529: } tikhomirov@529: tikhomirov@529: public void recordRemoved(Path fname) { tikhomirov@529: HgDirstate.Record r = forget(fname); tikhomirov@529: HgDirstate.Record n; tikhomirov@529: if (r == null) { tikhomirov@529: n = new HgDirstate.Record(0, -1, -1, fname, null); tikhomirov@529: } else { tikhomirov@529: n = new HgDirstate.Record(r.mode(), r.size(), r.modificationTime(), fname, r.copySource()); tikhomirov@529: } tikhomirov@529: removed.put(fname, n); tikhomirov@529: } tikhomirov@529: tikhomirov@529: private HgDirstate.Record forget(Path fname) { tikhomirov@529: HgDirstate.Record r; tikhomirov@529: if ((r = normal.remove(fname)) != null) { tikhomirov@529: return r; tikhomirov@529: } tikhomirov@529: if ((r = added.remove(fname)) != null) { tikhomirov@529: return r; tikhomirov@529: } tikhomirov@529: if ((r = removed.remove(fname)) != null) { tikhomirov@529: return r; tikhomirov@529: } tikhomirov@529: return 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@529: ByteBuffer recordTypes = ByteBuffer.allocate(4); tikhomirov@529: recordTypes.put((byte) 'n').put((byte) 'a').put((byte) 'r').put((byte) 'm').flip(); tikhomirov@526: for (Map m : all) { tikhomirov@529: final byte recordType = recordTypes.get(); 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@529: bb.put(recordType); 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: }