tikhomirov@10: /* tikhomirov@525: * Copyright (c) 2010-2013 TMate Software Ltd tikhomirov@74: * tikhomirov@74: * This program is free software; you can redistribute it and/or modify tikhomirov@74: * it under the terms of the GNU General Public License as published by tikhomirov@74: * the Free Software Foundation; version 2 of the License. tikhomirov@74: * tikhomirov@74: * This program is distributed in the hope that it will be useful, tikhomirov@74: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@74: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@74: * GNU General Public License for more details. tikhomirov@74: * tikhomirov@74: * For information on how to redistribute this software under tikhomirov@74: * the terms of a license other than GNU General Public License tikhomirov@102: * contact TMate Software at support@hg4j.com tikhomirov@10: */ tikhomirov@526: package org.tmatesoft.hg.internal; tikhomirov@10: tikhomirov@284: import static org.tmatesoft.hg.core.Nodeid.NULL; tikhomirov@527: import static org.tmatesoft.hg.repo.HgRepositoryFiles.Branch; tikhomirov@490: import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate; tikhomirov@456: import static org.tmatesoft.hg.util.LogFacility.Severity.Debug; tikhomirov@284: tikhomirov@252: import java.io.BufferedReader; tikhomirov@10: import java.io.File; tikhomirov@527: import java.io.FileInputStream; tikhomirov@348: import java.io.FileNotFoundException; tikhomirov@10: import java.io.IOException; tikhomirov@527: import java.io.InputStreamReader; tikhomirov@10: tikhomirov@231: import org.tmatesoft.hg.core.Nodeid; tikhomirov@526: import org.tmatesoft.hg.repo.HgDirstate; tikhomirov@526: import org.tmatesoft.hg.repo.HgDirstate.EntryKind; tikhomirov@526: import org.tmatesoft.hg.repo.HgInvalidControlFileException; tikhomirov@526: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@526: import org.tmatesoft.hg.util.LogFacility.Severity; tikhomirov@284: import org.tmatesoft.hg.util.Pair; tikhomirov@141: import org.tmatesoft.hg.util.Path; tikhomirov@74: tikhomirov@10: tikhomirov@10: /** tikhomirov@526: * Parse dirstate file tikhomirov@526: * tikhomirov@10: * @see http://mercurial.selenic.com/wiki/DirState tikhomirov@10: * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate tikhomirov@74: * tikhomirov@74: * @author Artem Tikhomirov tikhomirov@74: * @author TMate Software Ltd. tikhomirov@10: */ tikhomirov@526: public final class DirstateReader { tikhomirov@526: // dirstate read code originally lived in org.tmatesoft.hg.repo.HgDirstate tikhomirov@290: tikhomirov@490: private final Internals repo; tikhomirov@431: private final Path.Source pathPool; tikhomirov@284: private Pair parents; tikhomirov@252: tikhomirov@526: public DirstateReader(Internals hgRepo, Path.Source pathSource) { tikhomirov@252: repo = hgRepo; tikhomirov@431: pathPool = pathSource; tikhomirov@10: } tikhomirov@10: tikhomirov@526: public void readInto(HgDirstate.Inspector target) throws HgInvalidControlFileException { tikhomirov@490: EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper(); tikhomirov@371: parents = new Pair(Nodeid.NULL, Nodeid.NULL); tikhomirov@490: File dirstateFile = getDirstateFile(repo); tikhomirov@59: if (dirstateFile == null || !dirstateFile.exists()) { tikhomirov@10: return; tikhomirov@10: } tikhomirov@534: DataAccess da = repo.getDataAccess().createReader(dirstateFile); tikhomirov@10: try { tikhomirov@421: if (da.isEmpty()) { tikhomirov@421: return; tikhomirov@421: } tikhomirov@284: parents = internalReadParents(da); tikhomirov@227: // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only tikhomirov@227: while (!da.isEmpty()) { tikhomirov@10: final byte state = da.readByte(); tikhomirov@10: final int fmode = da.readInt(); tikhomirov@10: final int size = da.readInt(); tikhomirov@10: final int time = da.readInt(); tikhomirov@10: final int nameLen = da.readInt(); tikhomirov@10: String fn1 = null, fn2 = null; tikhomirov@10: byte[] name = new byte[nameLen]; tikhomirov@10: da.readBytes(name, 0, nameLen); tikhomirov@10: for (int i = 0; i < nameLen; i++) { tikhomirov@10: if (name[i] == 0) { tikhomirov@412: fn1 = encodingHelper.fromDirstate(name, 0, i); tikhomirov@412: fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1); tikhomirov@10: break; tikhomirov@10: } tikhomirov@10: } tikhomirov@10: if (fn1 == null) { tikhomirov@412: fn1 = encodingHelper.fromDirstate(name, 0, nameLen); tikhomirov@10: } tikhomirov@526: HgDirstate.Record r = new HgDirstate.Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2)); tikhomirov@10: if (state == 'n') { tikhomirov@526: target.next(EntryKind.Normal, r); tikhomirov@10: } else if (state == 'a') { tikhomirov@526: target.next(EntryKind.Added, r); tikhomirov@10: } else if (state == 'r') { tikhomirov@526: target.next(EntryKind.Removed, r); tikhomirov@10: } else if (state == 'm') { tikhomirov@526: target.next(EntryKind.Merged, r); tikhomirov@10: } else { tikhomirov@526: repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name(), r.size(), r.modificationTime(), state); tikhomirov@10: } tikhomirov@227: } tikhomirov@10: } catch (IOException ex) { tikhomirov@348: throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); tikhomirov@10: } finally { tikhomirov@10: da.done(); tikhomirov@10: } tikhomirov@10: } tikhomirov@10: tikhomirov@284: private static Pair internalReadParents(DataAccess da) throws IOException { tikhomirov@284: byte[] parents = new byte[40]; tikhomirov@284: da.readBytes(parents, 0, 40); tikhomirov@284: Nodeid n1 = Nodeid.fromBinary(parents, 0); tikhomirov@284: Nodeid n2 = Nodeid.fromBinary(parents, 20); tikhomirov@284: parents = null; tikhomirov@284: return new Pair(n1, n2); tikhomirov@284: } tikhomirov@284: tikhomirov@284: /** tikhomirov@290: * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values. tikhomirov@284: */ tikhomirov@284: public Pair parents() { tikhomirov@348: assert parents != null; // instance not initialized with #read() tikhomirov@284: return parents; tikhomirov@284: } tikhomirov@284: tikhomirov@490: private static File getDirstateFile(Internals repo) { tikhomirov@490: return repo.getFileFromRepoDir(Dirstate.getName()); tikhomirov@490: } tikhomirov@490: tikhomirov@284: /** tikhomirov@284: * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available tikhomirov@284: */ tikhomirov@526: public static Pair readParents(Internals internalRepo) throws HgInvalidControlFileException { tikhomirov@284: // do not read whole dirstate if all we need is WC parent information tikhomirov@490: File dirstateFile = getDirstateFile(internalRepo); tikhomirov@231: if (dirstateFile == null || !dirstateFile.exists()) { tikhomirov@284: return new Pair(NULL, NULL); tikhomirov@231: } tikhomirov@534: DataAccess da = internalRepo.getDataAccess().createReader(dirstateFile); tikhomirov@231: try { tikhomirov@421: if (da.isEmpty()) { tikhomirov@421: return new Pair(NULL, NULL); tikhomirov@421: } tikhomirov@284: return internalReadParents(da); tikhomirov@231: } catch (IOException ex) { tikhomirov@348: throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile); tikhomirov@231: } finally { tikhomirov@231: da.done(); tikhomirov@231: } tikhomirov@231: } tikhomirov@231: tikhomirov@231: /** tikhomirov@430: * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar tikhomirov@252: * @return branch associated with the working directory tikhomirov@252: */ tikhomirov@526: public static String readBranch(Internals internalRepo) throws HgInvalidControlFileException { tikhomirov@527: File branchFile = internalRepo.getRepositoryFile(Branch); tikhomirov@284: String branch = HgRepository.DEFAULT_BRANCH_NAME; tikhomirov@284: if (branchFile.exists()) { tikhomirov@284: try { tikhomirov@527: // branch file is UTF-8 encoded, see http://mercurial.selenic.com/wiki/EncodingStrategy#UTF-8_strings tikhomirov@527: // shall not use system-default encoding (FileReader) when reading it! tikhomirov@527: // Perhaps, shall use EncodingHelper.fromBranch and InputStream instead, for uniformity? tikhomirov@527: // Since whole file is in UTF8, InputStreamReader is a convenience over InputStream, tikhomirov@527: // which we use elsewhere (together with EncodingHelper) - other files are usually a mix of binary data tikhomirov@527: // and encoded text (hence, InputStreamReader with charset is not an option there) tikhomirov@527: BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(branchFile), EncodingHelper.getUTF8())); tikhomirov@284: String b = r.readLine(); tikhomirov@284: if (b != null) { tikhomirov@284: b = b.trim().intern(); tikhomirov@284: } tikhomirov@284: branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b; tikhomirov@284: r.close(); tikhomirov@348: } catch (FileNotFoundException ex) { tikhomirov@501: internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here tikhomirov@348: // IGNORE tikhomirov@284: } catch (IOException ex) { tikhomirov@348: throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile); tikhomirov@284: } tikhomirov@284: } tikhomirov@284: return branch; tikhomirov@284: } tikhomirov@10: }