kitaev@213: /* kitaev@213: * Copyright (c) 2010-2011 TMate Software Ltd kitaev@213: * kitaev@213: * This program is free software; you can redistribute it and/or modify kitaev@213: * it under the terms of the GNU General Public License as published by kitaev@213: * the Free Software Foundation; version 2 of the License. kitaev@213: * kitaev@213: * This program is distributed in the hope that it will be useful, kitaev@213: * but WITHOUT ANY WARRANTY; without even the implied warranty of kitaev@213: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the kitaev@213: * GNU General Public License for more details. kitaev@213: * kitaev@213: * For information on how to redistribute this software under kitaev@213: * the terms of a license other than GNU General Public License kitaev@213: * contact TMate Software at support@hg4j.com kitaev@213: */ kitaev@213: package org.tmatesoft.hg.repo; kitaev@213: kitaev@213: import java.io.File; kitaev@213: import java.io.IOException; kitaev@213: import java.util.Collections; kitaev@213: import java.util.LinkedHashMap; kitaev@213: import java.util.Map; kitaev@213: import java.util.TreeSet; kitaev@213: kitaev@213: import org.tmatesoft.hg.internal.DataAccess; kitaev@213: import org.tmatesoft.hg.internal.DataAccessProvider; kitaev@213: import org.tmatesoft.hg.util.Path; kitaev@213: kitaev@213: kitaev@213: /** kitaev@213: * @see http://mercurial.selenic.com/wiki/DirState kitaev@213: * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate kitaev@213: * kitaev@213: * @author Artem Tikhomirov kitaev@213: * @author TMate Software Ltd. kitaev@213: */ kitaev@213: class HgDirstate { kitaev@213: kitaev@213: private final DataAccessProvider accessProvider; kitaev@213: private final File dirstateFile; kitaev@213: // deliberate String, not Path as it seems useless to keep Path here kitaev@213: private Map normal; kitaev@213: private Map added; kitaev@213: private Map removed; kitaev@213: private Map merged; kitaev@213: kitaev@213: /*package-local*/ HgDirstate() { kitaev@213: // empty instance kitaev@213: accessProvider = null; kitaev@213: dirstateFile = null; kitaev@213: } kitaev@213: kitaev@213: public HgDirstate(DataAccessProvider dap, File dirstate) { kitaev@213: accessProvider = dap; kitaev@213: dirstateFile = dirstate; kitaev@213: } kitaev@213: kitaev@213: private void read() { kitaev@213: normal = added = removed = merged = Collections.emptyMap(); kitaev@213: if (dirstateFile == null || !dirstateFile.exists()) { kitaev@213: return; kitaev@213: } kitaev@213: DataAccess da = accessProvider.create(dirstateFile); kitaev@213: if (da.isEmpty()) { kitaev@213: return; kitaev@213: } kitaev@213: // not sure linked is really needed here, just for ease of debug kitaev@213: normal = new LinkedHashMap(); kitaev@213: added = new LinkedHashMap(); kitaev@213: removed = new LinkedHashMap(); kitaev@213: merged = new LinkedHashMap(); kitaev@213: try { kitaev@213: // XXX skip(40) if we don't need these? kitaev@213: byte[] parents = new byte[40]; kitaev@213: da.readBytes(parents, 0, 40); kitaev@213: parents = null; kitaev@213: do { kitaev@213: final byte state = da.readByte(); kitaev@213: final int fmode = da.readInt(); kitaev@213: final int size = da.readInt(); kitaev@213: final int time = da.readInt(); kitaev@213: final int nameLen = da.readInt(); kitaev@213: String fn1 = null, fn2 = null; kitaev@213: byte[] name = new byte[nameLen]; kitaev@213: da.readBytes(name, 0, nameLen); kitaev@213: for (int i = 0; i < nameLen; i++) { kitaev@213: if (name[i] == 0) { kitaev@213: fn1 = new String(name, 0, i, "UTF-8"); // XXX unclear from documentation what encoding is used there kitaev@213: fn2 = new String(name, i+1, nameLen - i - 1, "UTF-8"); // need to check with different system codepages kitaev@213: break; kitaev@213: } kitaev@213: } kitaev@213: if (fn1 == null) { kitaev@213: fn1 = new String(name); kitaev@213: } kitaev@213: Record r = new Record(fmode, size, time, fn1, fn2); kitaev@213: if (state == 'n') { kitaev@213: normal.put(r.name1, r); kitaev@213: } else if (state == 'a') { kitaev@213: added.put(r.name1, r); kitaev@213: } else if (state == 'r') { kitaev@213: removed.put(r.name1, r); kitaev@213: } else if (state == 'm') { kitaev@213: merged.put(r.name1, r); kitaev@213: } else { kitaev@213: // FIXME log error? kitaev@213: } kitaev@213: } while (!da.isEmpty()); kitaev@213: } catch (IOException ex) { kitaev@213: ex.printStackTrace(); // FIXME log error, clean dirstate? kitaev@213: } finally { kitaev@213: da.done(); kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: // new, modifiable collection kitaev@213: /*package-local*/ TreeSet all() { kitaev@213: read(); kitaev@213: TreeSet rv = new TreeSet(); kitaev@213: @SuppressWarnings("unchecked") kitaev@213: Map[] all = new Map[] { normal, added, removed, merged }; kitaev@213: for (int i = 0; i < all.length; i++) { kitaev@213: for (Record r : all[i].values()) { kitaev@213: rv.add(r.name1); kitaev@213: } kitaev@213: } kitaev@213: return rv; kitaev@213: } kitaev@213: kitaev@213: /*package-local*/ Record checkNormal(Path fname) { kitaev@213: return normal.get(fname.toString()); kitaev@213: } kitaev@213: kitaev@213: /*package-local*/ Record checkAdded(Path fname) { kitaev@213: return added.get(fname.toString()); kitaev@213: } kitaev@213: /*package-local*/ Record checkRemoved(Path fname) { kitaev@213: return removed.get(fname.toString()); kitaev@213: } kitaev@213: /*package-local*/ Record checkRemoved(String fname) { kitaev@213: return removed.get(fname); kitaev@213: } kitaev@213: /*package-local*/ Record checkMerged(Path fname) { kitaev@213: return merged.get(fname.toString()); kitaev@213: } kitaev@213: kitaev@213: kitaev@213: kitaev@213: kitaev@213: /*package-local*/ void dump() { kitaev@213: read(); kitaev@213: @SuppressWarnings("unchecked") kitaev@213: Map[] all = new Map[] { normal, added, removed, merged }; kitaev@213: char[] x = new char[] {'n', 'a', 'r', 'm' }; kitaev@213: for (int i = 0; i < all.length; i++) { kitaev@213: for (Record r : all[i].values()) { kitaev@213: System.out.printf("%c %3o%6d %30tc\t\t%s", x[i], r.mode, r.size, (long) r.time * 1000, r.name1); kitaev@213: if (r.name2 != null) { kitaev@213: System.out.printf(" --> %s", r.name2); kitaev@213: } kitaev@213: System.out.println(); kitaev@213: } kitaev@213: System.out.println(); kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: /*package-local*/ static class Record { kitaev@213: final int mode; kitaev@213: final int size; kitaev@213: final int time; kitaev@213: final String name1; kitaev@213: final String name2; kitaev@213: kitaev@213: public Record(int fmode, int fsize, int ftime, String name1, String name2) { kitaev@213: mode = fmode; kitaev@213: size = fsize; kitaev@213: time = ftime; kitaev@213: this.name1 = name1; kitaev@213: this.name2 = name2; kitaev@213: kitaev@213: } kitaev@213: } kitaev@213: }