tikhomirov@64: /* tikhomirov@445: * Copyright (c) 2011-2012 TMate Software Ltd tikhomirov@64: * tikhomirov@64: * This program is free software; you can redistribute it and/or modify tikhomirov@64: * it under the terms of the GNU General Public License as published by tikhomirov@64: * the Free Software Foundation; version 2 of the License. tikhomirov@64: * tikhomirov@64: * This program is distributed in the hope that it will be useful, tikhomirov@64: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@64: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@64: * GNU General Public License for more details. tikhomirov@64: * tikhomirov@64: * For information on how to redistribute this software under tikhomirov@64: * the terms of a license other than GNU General Public License tikhomirov@102: * contact TMate Software at support@hg4j.com tikhomirov@64: */ tikhomirov@64: package org.tmatesoft.hg.core; tikhomirov@64: tikhomirov@64: import java.util.ArrayList; tikhomirov@64: import java.util.Collections; tikhomirov@64: import java.util.List; tikhomirov@316: import java.util.Map; tikhomirov@64: tikhomirov@445: import org.tmatesoft.hg.internal.PhasesHelper; tikhomirov@450: import org.tmatesoft.hg.repo.HgChangelog; tikhomirov@154: import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; tikhomirov@445: import org.tmatesoft.hg.repo.HgPhase; tikhomirov@74: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@94: import org.tmatesoft.hg.repo.HgStatusCollector; tikhomirov@133: import org.tmatesoft.hg.util.Path; tikhomirov@64: tikhomirov@64: tikhomirov@64: /** tikhomirov@131: * Record in the Mercurial changelog, describing single commit. tikhomirov@131: * tikhomirov@64: * Not thread-safe, don't try to read from different threads tikhomirov@64: * tikhomirov@64: * @author Artem Tikhomirov tikhomirov@64: * @author TMate Software Ltd. tikhomirov@64: */ tikhomirov@129: public class HgChangeset implements Cloneable { tikhomirov@64: tikhomirov@450: // these get initialized tikhomirov@154: private RawChangeset changeset; tikhomirov@450: private int revNumber; tikhomirov@64: private Nodeid nodeid; tikhomirov@64: tikhomirov@450: class ShareDataStruct { tikhomirov@450: ShareDataStruct(HgStatusCollector statusCollector, Path.Source pathFactory) { tikhomirov@450: statusHelper = statusCollector; tikhomirov@450: pathHelper = pathFactory; tikhomirov@450: } tikhomirov@450: public final HgStatusCollector statusHelper; tikhomirov@450: public final Path.Source pathHelper; tikhomirov@450: tikhomirov@450: public HgChangelog.ParentWalker parentHelper; tikhomirov@450: public PhasesHelper phaseHelper; tikhomirov@450: }; tikhomirov@450: tikhomirov@450: // Helpers/utilities shared among few instances of HgChangeset tikhomirov@450: private final ShareDataStruct shared; tikhomirov@450: tikhomirov@450: // these are built on demand tikhomirov@231: private List modifiedFiles, addedFiles; tikhomirov@64: private List deletedFiles; tikhomirov@195: private byte[] parent1, parent2; tikhomirov@450: tikhomirov@64: tikhomirov@64: // XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new tikhomirov@64: // and pass it around tikhomirov@142: /*package-local*/HgChangeset(HgStatusCollector statusCollector, Path.Source pathFactory) { tikhomirov@450: shared = new ShareDataStruct(statusCollector, pathFactory); tikhomirov@64: } tikhomirov@195: tikhomirov@195: /*package-local*/ void init(int localRevNumber, Nodeid nid, RawChangeset rawChangeset) { tikhomirov@64: revNumber = localRevNumber; tikhomirov@64: nodeid = nid; tikhomirov@196: changeset = rawChangeset.clone(); tikhomirov@65: modifiedFiles = addedFiles = null; tikhomirov@65: deletedFiles = null; tikhomirov@195: parent1 = parent2 = null; tikhomirov@450: // keep references to shared (and everything in there: parentHelper, statusHelper, phaseHelper and pathHelper) tikhomirov@64: } tikhomirov@195: tikhomirov@195: /*package-local*/ void setParentHelper(HgChangelog.ParentWalker pw) { tikhomirov@450: if (pw != null) { tikhomirov@450: if (pw.getRepo() != shared.statusHelper.getRepo()) { tikhomirov@195: throw new IllegalArgumentException(); tikhomirov@195: } tikhomirov@195: } tikhomirov@450: shared.parentHelper = pw; tikhomirov@195: } tikhomirov@195: tikhomirov@65: public int getRevision() { tikhomirov@65: return revNumber; tikhomirov@65: } tikhomirov@65: public Nodeid getNodeid() { tikhomirov@65: return nodeid; tikhomirov@65: } tikhomirov@64: public String getUser() { tikhomirov@64: return changeset.user(); tikhomirov@64: } tikhomirov@64: public String getComment() { tikhomirov@64: return changeset.comment(); tikhomirov@64: } tikhomirov@64: public String getBranch() { tikhomirov@64: return changeset.branch(); tikhomirov@64: } tikhomirov@211: tikhomirov@211: /** tikhomirov@211: * @return used to be String, now {@link HgDate}, use {@link HgDate#toString()} to get same result as before tikhomirov@211: */ tikhomirov@211: public HgDate getDate() { tikhomirov@211: return new HgDate(changeset.date().getTime(), changeset.timezone()); tikhomirov@64: } tikhomirov@65: public Nodeid getManifestRevision() { tikhomirov@65: return changeset.manifest(); tikhomirov@65: } tikhomirov@64: tikhomirov@64: public List getAffectedFiles() { tikhomirov@87: // reports files as recorded in changelog. Note, merge revisions may have no tikhomirov@87: // files listed, and thus this method would return empty list, while tikhomirov@87: // #getModifiedFiles() would return list with merged file(s) (because it uses status to get 'em, not tikhomirov@87: // what #files() gives). tikhomirov@64: ArrayList rv = new ArrayList(changeset.files().size()); tikhomirov@64: for (String name : changeset.files()) { tikhomirov@450: rv.add(shared.pathHelper.path(name)); tikhomirov@64: } tikhomirov@64: return rv; tikhomirov@64: } tikhomirov@64: tikhomirov@366: public List getModifiedFiles() throws HgInvalidControlFileException { tikhomirov@64: if (modifiedFiles == null) { tikhomirov@64: initFileChanges(); tikhomirov@64: } tikhomirov@64: return modifiedFiles; tikhomirov@64: } tikhomirov@64: tikhomirov@366: public List getAddedFiles() throws HgInvalidControlFileException { tikhomirov@64: if (addedFiles == null) { tikhomirov@64: initFileChanges(); tikhomirov@64: } tikhomirov@64: return addedFiles; tikhomirov@64: } tikhomirov@64: tikhomirov@366: public List getRemovedFiles() throws HgInvalidControlFileException { tikhomirov@64: if (deletedFiles == null) { tikhomirov@64: initFileChanges(); tikhomirov@64: } tikhomirov@64: return deletedFiles; tikhomirov@64: } tikhomirov@64: tikhomirov@366: public boolean isMerge() throws HgInvalidControlFileException { tikhomirov@195: // p1 == -1 and p2 != -1 is legitimate case tikhomirov@274: return !(getFirstParentRevision().isNull() || getSecondParentRevision().isNull()); tikhomirov@125: } tikhomirov@124: tikhomirov@274: /** tikhomirov@274: * @return never null tikhomirov@380: * @throws HgInvalidControlFileException if access to revlog index/data entry failed tikhomirov@274: */ tikhomirov@366: public Nodeid getFirstParentRevision() throws HgInvalidControlFileException { tikhomirov@450: if (shared.parentHelper != null) { tikhomirov@450: return shared.parentHelper.safeFirstParent(nodeid); tikhomirov@195: } tikhomirov@195: // read once for both p1 and p2 tikhomirov@195: if (parent1 == null) { tikhomirov@195: parent1 = new byte[20]; tikhomirov@195: parent2 = new byte[20]; tikhomirov@445: getRepo().getChangelog().parents(revNumber, new int[2], parent1, parent2); tikhomirov@195: } tikhomirov@195: return Nodeid.fromBinary(parent1, 0); tikhomirov@124: } tikhomirov@124: tikhomirov@274: /** tikhomirov@274: * @return never null tikhomirov@380: * @throws HgInvalidControlFileException if access to revlog index/data entry failed tikhomirov@274: */ tikhomirov@366: public Nodeid getSecondParentRevision() throws HgInvalidControlFileException { tikhomirov@450: if (shared.parentHelper != null) { tikhomirov@450: return shared.parentHelper.safeSecondParent(nodeid); tikhomirov@195: } tikhomirov@195: if (parent2 == null) { tikhomirov@195: parent1 = new byte[20]; tikhomirov@195: parent2 = new byte[20]; tikhomirov@445: getRepo().getChangelog().parents(revNumber, new int[2], parent1, parent2); tikhomirov@195: } tikhomirov@195: return Nodeid.fromBinary(parent2, 0); tikhomirov@124: } tikhomirov@445: tikhomirov@445: /** tikhomirov@445: * Tells the phase this changeset belongs to. tikhomirov@445: * @return one of {@link HgPhase} values tikhomirov@445: */ tikhomirov@445: public HgPhase getPhase() throws HgInvalidControlFileException { tikhomirov@450: if (shared.phaseHelper == null) { tikhomirov@445: // XXX would be handy to obtain ProgressSupport (perhaps, from statusHelper?) tikhomirov@445: // and pass it to #init(), so that there could be indication of file being read and cache being built tikhomirov@450: synchronized (shared) { tikhomirov@450: // ensure field is initialized only once tikhomirov@450: if (shared.phaseHelper == null) { tikhomirov@450: shared.phaseHelper = new PhasesHelper(getRepo(), shared.parentHelper); tikhomirov@450: } tikhomirov@450: } tikhomirov@445: } tikhomirov@450: return shared.phaseHelper.getPhase(this); tikhomirov@445: } tikhomirov@124: tikhomirov@64: @Override tikhomirov@129: public HgChangeset clone() { tikhomirov@64: try { tikhomirov@129: HgChangeset copy = (HgChangeset) super.clone(); tikhomirov@196: // copy.changeset references this.changeset, doesn't need own copy tikhomirov@64: return copy; tikhomirov@64: } catch (CloneNotSupportedException ex) { tikhomirov@64: throw new InternalError(ex.toString()); tikhomirov@64: } tikhomirov@64: } tikhomirov@445: tikhomirov@445: private HgRepository getRepo() { tikhomirov@450: return shared.statusHelper.getRepo(); tikhomirov@445: } tikhomirov@64: tikhomirov@366: private /*synchronized*/ void initFileChanges() throws HgInvalidControlFileException { tikhomirov@64: ArrayList deleted = new ArrayList(); tikhomirov@231: ArrayList modified = new ArrayList(); tikhomirov@231: ArrayList added = new ArrayList(); tikhomirov@94: HgStatusCollector.Record r = new HgStatusCollector.Record(); tikhomirov@450: shared.statusHelper.change(revNumber, r); tikhomirov@445: final HgRepository repo = getRepo(); tikhomirov@93: for (Path s : r.getModified()) { tikhomirov@64: Nodeid nid = r.nodeidAfterChange(s); tikhomirov@64: if (nid == null) { tikhomirov@148: throw new HgBadStateException(); tikhomirov@64: } tikhomirov@316: modified.add(new HgFileRevision(repo, nid, s, null)); tikhomirov@64: } tikhomirov@316: final Map copied = r.getCopied(); tikhomirov@93: for (Path s : r.getAdded()) { tikhomirov@64: Nodeid nid = r.nodeidAfterChange(s); tikhomirov@64: if (nid == null) { tikhomirov@148: throw new HgBadStateException(); tikhomirov@64: } tikhomirov@316: added.add(new HgFileRevision(repo, nid, s, copied.get(s))); tikhomirov@64: } tikhomirov@93: for (Path s : r.getRemoved()) { tikhomirov@124: // with Path from getRemoved, may just copy tikhomirov@93: deleted.add(s); tikhomirov@64: } tikhomirov@64: modified.trimToSize(); tikhomirov@64: added.trimToSize(); tikhomirov@64: deleted.trimToSize(); tikhomirov@64: modifiedFiles = Collections.unmodifiableList(modified); tikhomirov@64: addedFiles = Collections.unmodifiableList(added); tikhomirov@64: deletedFiles = Collections.unmodifiableList(deleted); tikhomirov@64: } tikhomirov@64: }