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.core; tikhomirov@525: tikhomirov@527: import static org.tmatesoft.hg.repo.HgRepositoryFiles.Branch; tikhomirov@526: import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate; tikhomirov@526: tikhomirov@525: import java.io.File; tikhomirov@525: import java.io.FileOutputStream; tikhomirov@525: import java.io.IOException; tikhomirov@527: import java.io.OutputStreamWriter; tikhomirov@525: import java.nio.channels.FileChannel; tikhomirov@525: tikhomirov@525: import org.tmatesoft.hg.internal.DirstateBuilder; tikhomirov@527: import org.tmatesoft.hg.internal.EncodingHelper; tikhomirov@525: import org.tmatesoft.hg.internal.Experimental; tikhomirov@525: import org.tmatesoft.hg.internal.Internals; tikhomirov@525: import org.tmatesoft.hg.internal.WorkingDirFileWriter; tikhomirov@525: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@525: import org.tmatesoft.hg.repo.HgInvalidRevisionException; tikhomirov@525: import org.tmatesoft.hg.repo.HgManifest; tikhomirov@526: import org.tmatesoft.hg.repo.HgManifest.Flags; tikhomirov@525: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@525: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@525: import org.tmatesoft.hg.util.CancelledException; tikhomirov@525: import org.tmatesoft.hg.util.Path; tikhomirov@525: tikhomirov@525: /** tikhomirov@525: * WORK IN PROGRESS. tikhomirov@525: * tikhomirov@525: * Update working directory to specific state, 'hg checkout' counterpart. tikhomirov@525: * For the time being, only 'clean' checkout is supported ('hg co --clean') tikhomirov@525: * tikhomirov@525: * @since 1.1 tikhomirov@525: * @author Artem Tikhomirov tikhomirov@525: * @author TMate Software Ltd. tikhomirov@525: */ tikhomirov@525: @Experimental(reason="Work in progress") tikhomirov@525: public class HgCheckoutCommand extends HgAbstractCommand{ tikhomirov@525: tikhomirov@525: private final HgRepository repo; tikhomirov@525: private int revisionToCheckout = HgRepository.BAD_REVISION; tikhomirov@525: tikhomirov@525: public HgCheckoutCommand(HgRepository hgRepo) { tikhomirov@525: repo = hgRepo; tikhomirov@525: } tikhomirov@525: tikhomirov@525: /** tikhomirov@525: * Select revision to check out tikhomirov@525: * tikhomirov@525: * @param nodeid revision tikhomirov@525: * @return this for convenience tikhomirov@525: * @throws HgBadArgumentException if failed to find supplied changeset tikhomirov@525: */ tikhomirov@525: public HgCheckoutCommand changeset(Nodeid nodeid) throws HgBadArgumentException { tikhomirov@525: try { tikhomirov@525: return changeset(repo.getChangelog().getRevisionIndex(nodeid)); tikhomirov@525: } catch (HgInvalidRevisionException ex) { tikhomirov@525: throw new HgBadArgumentException("Can't find revision", ex).setRevision(nodeid); tikhomirov@525: } tikhomirov@525: } tikhomirov@525: tikhomirov@525: /** tikhomirov@525: * Select revision to check out using local revision index tikhomirov@525: * tikhomirov@525: * @param changesetIndex local revision index tikhomirov@525: * @return this for convenience tikhomirov@525: * @throws HgBadArgumentException if failed to find supplied changeset tikhomirov@525: */ tikhomirov@525: public HgCheckoutCommand changeset(int changesetIndex) throws HgBadArgumentException { tikhomirov@525: int lastCsetIndex = repo.getChangelog().getLastRevision(); tikhomirov@525: if (changesetIndex < 0 || changesetIndex > lastCsetIndex) { tikhomirov@525: throw new HgBadArgumentException(String.format("Bad revision index %d, value from [0..%d] expected", changesetIndex, lastCsetIndex), null).setRevisionIndex(changesetIndex); tikhomirov@525: } tikhomirov@525: revisionToCheckout = changesetIndex; tikhomirov@525: return this; tikhomirov@525: } tikhomirov@525: tikhomirov@525: /** tikhomirov@525: * tikhomirov@525: * @throws HgIOException to indicate troubles updating files in working copy tikhomirov@525: * @throws HgException tikhomirov@525: * @throws CancelledException tikhomirov@525: */ tikhomirov@525: public void execute() throws HgException, CancelledException { tikhomirov@526: try { tikhomirov@526: Internals internalRepo = Internals.getInstance(repo); tikhomirov@526: // FIXME remove tracked files from wd (perhaps, just forget 'Added'?) tikhomirov@526: // TODO tikhomirov@526: final DirstateBuilder dirstateBuilder = new DirstateBuilder(internalRepo); tikhomirov@526: final CheckoutWorker worker = new CheckoutWorker(internalRepo); tikhomirov@526: HgManifest.Inspector insp = new HgManifest.Inspector() { tikhomirov@526: tikhomirov@526: public boolean next(Nodeid nid, Path fname, Flags flags) { tikhomirov@526: if (worker.next(nid, fname, flags)) { tikhomirov@526: // new dirstate based on manifest tikhomirov@526: dirstateBuilder.recordNormal(fname, flags, worker.getLastWrittenFileSize()); tikhomirov@526: return true; tikhomirov@526: } tikhomirov@526: return false; tikhomirov@526: } tikhomirov@526: tikhomirov@526: public boolean end(int manifestRevision) { tikhomirov@526: return false; tikhomirov@526: } tikhomirov@526: tikhomirov@526: public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) { tikhomirov@525: return true; tikhomirov@525: } tikhomirov@526: }; tikhomirov@526: dirstateBuilder.parents(repo.getChangelog().getRevision(revisionToCheckout), null); tikhomirov@526: repo.getManifest().walk(revisionToCheckout, revisionToCheckout, insp); tikhomirov@526: worker.checkFailed(); tikhomirov@526: File dirstateFile = internalRepo.getRepositoryFile(Dirstate); tikhomirov@526: try { tikhomirov@526: FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel(); tikhomirov@526: dirstateBuilder.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@525: } tikhomirov@527: String branchName = repo.getChangelog().range(revisionToCheckout, revisionToCheckout).get(0).branch(); tikhomirov@527: assert branchName != null; tikhomirov@527: if (!HgRepository.DEFAULT_BRANCH_NAME.equals(branchName)) { tikhomirov@527: File branchFile = internalRepo.getRepositoryFile(Branch); tikhomirov@527: try { tikhomirov@527: // branch file is UTF-8, see http://mercurial.selenic.com/wiki/EncodingStrategy#UTF-8_strings tikhomirov@527: OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream(branchFile), EncodingHelper.getUTF8()); tikhomirov@527: ow.write(branchName); tikhomirov@527: ow.close(); tikhomirov@527: } catch (IOException ex) { tikhomirov@527: throw new HgIOException("Can't write down branch information", ex, branchFile); tikhomirov@527: } tikhomirov@527: } tikhomirov@526: } catch (HgRuntimeException ex) { tikhomirov@526: throw new HgLibraryFailureException(ex); tikhomirov@526: } tikhomirov@526: } tikhomirov@526: tikhomirov@526: static class CheckoutWorker { tikhomirov@526: private final Internals hgRepo; tikhomirov@526: private HgException failure; tikhomirov@526: private int lastWrittenFileSize; tikhomirov@526: tikhomirov@526: CheckoutWorker(Internals implRepo) { tikhomirov@526: hgRepo = implRepo; tikhomirov@526: } tikhomirov@526: tikhomirov@526: public boolean next(Nodeid nid, Path fname, Flags flags) { tikhomirov@526: try { tikhomirov@526: HgDataFile df = hgRepo.getRepo().getFileNode(fname); tikhomirov@526: int fileRevIndex = df.getRevisionIndex(nid); tikhomirov@526: // check out files based on manifest tikhomirov@526: // FIXME links! tikhomirov@526: WorkingDirFileWriter workingDirWriter = new WorkingDirFileWriter(hgRepo); tikhomirov@526: workingDirWriter.processFile(df, fileRevIndex); tikhomirov@526: lastWrittenFileSize = workingDirWriter.bytesWritten(); tikhomirov@525: return true; tikhomirov@526: } catch (IOException ex) { tikhomirov@526: failure = new HgIOException("Failed to write down file revision", ex, /*FIXME file*/null); tikhomirov@526: } catch (HgRuntimeException ex) { tikhomirov@526: failure = new HgLibraryFailureException(ex); tikhomirov@525: } tikhomirov@526: return false; tikhomirov@525: } tikhomirov@526: tikhomirov@526: public int getLastWrittenFileSize() { tikhomirov@526: return lastWrittenFileSize; tikhomirov@525: } tikhomirov@526: tikhomirov@526: public void checkFailed() throws HgException { tikhomirov@526: if (failure != null) { tikhomirov@526: throw failure; tikhomirov@526: } tikhomirov@526: } tikhomirov@526: }; tikhomirov@525: }