Package org.tmatesoft.hg.repo

Source Code of org.tmatesoft.hg.repo.Revlog$ParentDelegate

/*
* Copyright (c) 2010-2013 TMate Software Ltd
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* For information on how to redistribute this software under
* the terms of a license other than GNU General Public License
* contact TMate Software at support@hg4j.com
*/
package org.tmatesoft.hg.repo;

import static org.tmatesoft.hg.repo.HgRepository.*;
import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.internal.DataAccess;
import org.tmatesoft.hg.internal.Experimental;
import org.tmatesoft.hg.internal.IntMap;
import org.tmatesoft.hg.internal.Preview;
import org.tmatesoft.hg.internal.RevisionLookup;
import org.tmatesoft.hg.internal.RevlogDelegate;
import org.tmatesoft.hg.internal.RevlogStream;
import org.tmatesoft.hg.util.Adaptable;
import org.tmatesoft.hg.util.ByteChannel;
import org.tmatesoft.hg.util.CancelSupport;
import org.tmatesoft.hg.util.CancelledException;
import org.tmatesoft.hg.util.LogFacility;
import org.tmatesoft.hg.util.ProgressSupport;


/**
* Base class for all Mercurial entities that are serialized in a so called revlog format (changelog, manifest, data files).
*
* Implementation note:
* Hides actual actual revlog stream implementation and its access methods (i.e. RevlogStream.Inspector), iow shall not expose anything internal
* in public methods.
*  
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
abstract class Revlog {

  private final HgRepository repo;
  protected final RevlogStream content;
  protected final boolean useRevisionLookup;
  protected RevisionLookup revisionLookup;
  private final RevlogStream.Observer revisionLookupCleaner;

  protected Revlog(HgRepository hgRepo, RevlogStream contentStream, boolean needRevisionLookup) {
    if (hgRepo == null) {
      throw new IllegalArgumentException();
    }
    if (contentStream == null) {
      throw new IllegalArgumentException();
    }
    repo = hgRepo;
    content = contentStream;
    useRevisionLookup = needRevisionLookup;
    if (needRevisionLookup) {
      revisionLookupCleaner = new RevlogStream.Observer() {
       
        public void reloaded(RevlogStream src) {
          revisionLookup = null;
        }
      };
    } else {
      revisionLookupCleaner = null;
    }
  }
 
  // invalid Revlog
  protected Revlog(HgRepository hgRepo) {
    repo = hgRepo;
    content = null;
    useRevisionLookup = false;
    revisionLookupCleaner = null;
  }

  public final HgRepository getRepo() {
    return repo;
  }

  /**
   * @return total number of revisions kept in this revlog
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  public final int getRevisionCount() throws HgRuntimeException {
    return content.revisionCount();
  }
 
  /**
   * @return index of last known revision, a.k.a. {@link HgRepository#TIP}, or {@link HgRepository#NO_REVISION} if revlog is empty
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  public final int getLastRevision() throws HgRuntimeException {
    // although old code gives correct result when revlog is empty (NO_REVISION deliberately == -1),
    // it's still better to be explicit
    int revCount = content.revisionCount();
    return revCount == 0 ? NO_REVISION : revCount - 1;
  }

  /**
   * Map revision index to unique revision identifier (nodeid).
   * 
   * @param revisionIndex index of the entry in this revlog, may be {@link HgRepository#TIP}
   * @return revision nodeid of the entry
   *
   * @throws HgInvalidRevisionException if any supplied revision doesn't identify revision from this revlog. <em>Runtime exception</em>
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  public final Nodeid getRevision(int revisionIndex) throws HgRuntimeException {
    // XXX cache nodeids? Rather, if context.getCache(this).getRevisionMap(create == false) != null, use it
    return Nodeid.fromBinary(content.nodeid(revisionIndex), 0);
  }
 
  /**
   * Effective alternative to map few revision indexes to corresponding nodeids at once.
   * <p>Note, there are few aspects to be careful about when using this method<ul>
   * <li>ordering of the revisions in the return list is unspecified, it's likely won't match that of the method argument
   * <li>supplied array get modified (sorted)</ul>
   * @return list of mapped revisions in no particular order
   * @throws HgInvalidRevisionException if any supplied revision doesn't identify revision from this revlog. <em>Runtime exception</em>
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  public final List<Nodeid> getRevisions(int... revisions) throws HgRuntimeException {
    ArrayList<Nodeid> rv = new ArrayList<Nodeid>(revisions.length);
    Arrays.sort(revisions);
    getRevisionsInternal(rv, revisions);
    return rv;
  }
 
  /*package-local*/ void getRevisionsInternal(final List<Nodeid> retVal, int[] sortedRevs) throws HgRuntimeException {
    // once I have getRevisionMap and may find out whether it is avalable from cache,
    // may use it, perhaps only for small number of revisions
    content.iterate(sortedRevs, false, new RevlogStream.Inspector() {
     
      public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
        retVal.add(Nodeid.fromBinary(nodeid, 0));
      }
    });
  }

  /**
   * Get local index of the specified revision.
   * If unsure, use {@link #isKnown(Nodeid)} to find out whether nodeid belongs to this revlog.
   *
   * For occasional queries, this method works with decent performance, despite its O(n/2) approach.
   * Alternatively, if you need to perform multiple queries (e.g. at least 15-20), {@link HgRevisionMap} may come handy.
   *
   * @param nid revision to look up
   * @return revision local index in this revlog
   * @throws HgInvalidRevisionException if revision was not found in this revlog. <em>Runtime exception</em>
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  public final int getRevisionIndex(Nodeid nid) throws HgRuntimeException {
    final int revision = doFindWithCache(nid);
    if (revision == BAD_REVISION) {
      // using toString() to identify revlog. HgDataFile.toString includes path, HgManifest and HgChangelog instances
      // are fine with default (class name)
      // Perhaps, more tailored description method would be suitable here
      throw new HgInvalidRevisionException(String.format("Can't find revision %s in %s", nid.shortNotation(), this), nid, null);
    }
    return revision;
  }
 
  private int doFindWithCache(Nodeid nid) throws HgRuntimeException {
    if (useRevisionLookup) {
      if (revisionLookup == null || content.shallDropDerivedCaches()) {
        content.detach(revisionLookupCleaner);
        setRevisionLookup(RevisionLookup.createFor(content));
      }
      return revisionLookup.findIndex(nid);
    } else {
      return content.findRevisionIndex(nid);
    }
  }
 
  /**
   * use selected helper for revision lookup, register appropriate listeners to clear cache on revlog changes
   * @param rl not <code>null</code>
   */
  protected void setRevisionLookup(RevisionLookup rl) {
    assert rl != null;
    revisionLookup = rl;
    content.attach(revisionLookupCleaner);
  }
 
  /**
   * Note, {@link Nodeid#NULL} nodeid is not reported as known in any revlog.
   *
   * @param nodeid
   * @return <code>true</code> if revision is part of this revlog
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  public final boolean isKnown(Nodeid nodeid) throws HgRuntimeException {
    final int rn = doFindWithCache(nodeid);
    if (BAD_REVISION == rn) {
      return false;
    }
    if (rn < 0 || rn >= content.revisionCount()) {
      // Sanity check
      throw new HgInvalidStateException(String.format("Revision index %d found for nodeid %s is not from the range [0..%d]", rn, nodeid.shortNotation(), content.revisionCount()-1));
    }
    return true;
  }

  /**
   * Access to revision data as is, equivalent to <code>rawContent(getRevisionIndex(nodeid), sink)</code>
   *
   * @param nodeid revision to retrieve
   * @param sink data destination
   *
   * @see #rawContent(int, ByteChannel)
   *
   * @throws CancelledException if content retrieval operation was cancelled
   * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog. <em>Runtime exception</em>
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  protected void rawContent(Nodeid nodeid, ByteChannel sink) throws CancelledException, HgRuntimeException {
    rawContent(getRevisionIndex(nodeid), sink);
  }
 
  /**
   * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries).
   * 
   * @param revisionIndex index of this revlog change (not a changelog revision index), non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense.
   * @param sink data destination
   *
   * @throws CancelledException if content retrieval operation was cancelled
   * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog. <em>Runtime exception</em>
   * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em>
   * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
   */
  protected void rawContent(int revisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException {
    if (sink == null) {
      throw new IllegalArgumentException();
    }
    try {
      ContentPipe insp = new ContentPipe(sink, 0, repo.getSessionContext().getLog());
      insp.checkCancelled();
      content.iterate(revisionIndex, revisionIndex, true, insp);
      insp.checkFailed();
    } catch (IOException ex) {
      HgInvalidControlFileException e = new HgInvalidControlFileException(String.format("Access to revision %d content failed", revisionIndex), ex, null);
      e.setRevisionIndex(revisionIndex);
      // TODO [post 1.1] e.setFileName(content.getIndexFile() or this.getHumanFriendlyPath()) - shall decide whether
      // protected abstract getHFPath() with impl in HgDataFile, HgManifest and HgChangelog or path is data of either Revlog or RevlogStream
      // Do the same (add file name) below
      throw e;
    } catch (HgInvalidControlFileException ex) {
      throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionIndex);
    }
  }

  /**
   * Fills supplied arguments with information about revision parents.
   *
   * @param revision - revision to query parents, or {@link HgRepository#TIP}
   * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1}), {@link HgRepository#NO_REVISION} indicates parent not set
   * @param parent1 - byte[20] or null, if parent's nodeid is not needed
   * @param parent2 - byte[20] or null, if second parent's nodeid is not needed
   * @throws IllegalArgumentException if passed arrays can't fit requested data
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgRuntimeException, IllegalArgumentException {
    if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) {
      throw new HgInvalidRevisionException(revision);
    }
    if (parentRevisions == null || parentRevisions.length < 2) {
      throw new IllegalArgumentException(String.valueOf(parentRevisions));
    }
    if (parent1 != null && parent1.length < 20) {
      throw new IllegalArgumentException(parent1.toString());
    }
    if (parent2 != null && parent2.length < 20) {
      throw new IllegalArgumentException(parent2.toString());
    }
    class ParentCollector implements RevlogStream.Inspector {
      public int p1 = -1;
      public int p2 = -1;
      public byte[] nodeid;
     
      public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
        p1 = parent1Revision;
        p2 = parent2Revision;
        this.nodeid = new byte[20];
        // nodeid arg now comes in 32 byte from (as in file format description), however upper 12 bytes are zeros.
        System.arraycopy(nodeid, nodeid.length > 20 ? nodeid.length - 20 : 0, this.nodeid, 0, 20);
      }
    };
    ParentCollector pc = new ParentCollector();
    content.iterate(revision, revision, false, pc);
    // although next code looks odd (NO_REVISION *is* -1), it's safer to be explicit
    parentRevisions[0] = pc.p1 == -1 ? NO_REVISION : pc.p1;
    parentRevisions[1] = pc.p2 == -1 ? NO_REVISION : pc.p2;
    if (parent1 != null) {
      if (parentRevisions[0] == NO_REVISION) {
        Arrays.fill(parent1, 0, 20, (byte) 0);
      } else {
        content.iterate(parentRevisions[0], parentRevisions[0], false, pc);
        System.arraycopy(pc.nodeid, 0, parent1, 0, 20);
      }
    }
    if (parent2 != null) {
      if (parentRevisions[1] == NO_REVISION) {
        Arrays.fill(parent2, 0, 20, (byte) 0);
      } else {
        content.iterate(parentRevisions[1], parentRevisions[1], false, pc);
        System.arraycopy(pc.nodeid, 0, parent2, 0, 20);
      }
    }
  }

  /**
   * EXPERIMENTAL CODE, DO NOT USE
   *
   * Alternative revlog iteration
   * There's an implicit ordering of inspectors. Less inspector needs, earlier its invocation occurs.
   * E.g. ParentInspector goes after RevisionInspector. LinkRevisionInspector goes before RevisionInspector
   *
   * @param start
   * @param end
   * @param inspector
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  @Experimental
  public final void indexWalk(int start, int end, final Revlog.Inspector inspector) throws HgRuntimeException {
    int lastRev = getLastRevision();
    final int _start = start == TIP ? lastRev : start;
    if (end == TIP) {
      end = lastRev;
    }
    final RevisionInspector revisionInsp = Adaptable.Factory.getAdapter(inspector, RevisionInspector.class, null);
    final ParentInspector parentInsp = Adaptable.Factory.getAdapter(inspector, ParentInspector.class, null);
    final LinkRevisionInspector linkRevInspector = Adaptable.Factory.getAdapter(inspector, LinkRevisionInspector.class, null);
   
    // instantiate implementors in reverse order
    RevlogDelegate head = parentInsp == null ? null : new ParentDelegate(parentInsp, null, _start, end);
    if (revisionInsp != null) {
      head = new RevisionDelegate(revisionInsp, head);
    }
    if (linkRevInspector != null) {
      head = new LinkRevDelegate(linkRevInspector, head);
    }
    // first to get notified is created last

    assert head != null; // we know all subclasses of Revlog.Inspector
    head.walk(getRepo(), content, _start, end);
  }
 
  /**
   * MARKER
   */
  @Experimental
  public interface Inspector {
  }

  @Experimental
  public interface RevisionInspector extends Inspector {
    void next(int revisionIndex, Nodeid revision, int linkedRevisionIndex) throws HgRuntimeException;
  }

  @Experimental
  public interface LinkRevisionInspector extends Inspector {
    void next(int revisionIndex, int linkedRevisionIndex) throws HgRuntimeException;
  }

  @Experimental
  public interface ParentInspector extends Inspector {
    // XXX document whether parentX is -1 or a constant (BAD_REVISION? or dedicated?)
    void next(int revisionIndex, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) throws HgRuntimeException;
  }
 
  protected HgParentChildMap<? extends Revlog> getParentWalker() throws HgRuntimeException {
    HgParentChildMap<Revlog> pw = new HgParentChildMap<Revlog>(this);
    pw.init();
    return pw;
  }
 
  /*
   * class with cancel and few other exceptions support.
   * TODO [post-1.1] consider general superclass to share with e.g. HgManifestCommand.Mediator
   */
  protected abstract static class ErrorHandlingInspector implements RevlogStream.Inspector, CancelSupport {
    private Exception failure;
    private CancelSupport cancelSupport;
   
    protected void setCancelSupport(CancelSupport cs) {
      assert cancelSupport == null; // no reason to set it twice
      cancelSupport = cs;
    }

    protected void recordFailure(Exception ex) {
      assert failure == null;
      failure = ex;
    }

    public void checkFailed() throws HgRuntimeException, IOException, CancelledException {
      if (failure == null) {
        return;
      }
      if (failure instanceof IOException) {
        throw (IOException) failure;
      }
      if (failure instanceof CancelledException) {
        throw (CancelledException) failure;
      }
      if (failure instanceof HgRuntimeException) {
        throw (HgRuntimeException) failure;
      }
      throw new HgInvalidStateException(failure.toString());
    }

    public void checkCancelled() throws CancelledException {
      if (cancelSupport != null) {
        cancelSupport.checkCancelled();
      }
    }
  }

  protected static class ContentPipe extends ErrorHandlingInspector implements RevlogStream.Inspector, CancelSupport {
    private final ByteChannel sink;
    private final int offset;
    private final LogFacility logFacility;

    /**
     * @param _sink - cannot be <code>null</code>
     * @param seekOffset - when positive, orders to pipe bytes to the sink starting from specified offset, not from the first byte available in DataAccess
     * @param log optional facility to put warnings/debug messages into, may be null.
     */
    public ContentPipe(ByteChannel _sink, int seekOffset, LogFacility log) {
      assert _sink != null;
      sink = _sink;
      setCancelSupport(CancelSupport.Factory.get(_sink));
      offset = seekOffset;
      logFacility = log;
    }
   
    protected void prepare(int revisionNumber, DataAccess da) throws IOException {
      if (offset > 0) { // save few useless reset/rewind operations
        da.seek(offset);
      }
    }

    public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
      try {
        prepare(revisionNumber, da); // XXX perhaps, prepare shall return DA (sliced, if needed)
        final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink);
        ByteBuffer buf = ByteBuffer.allocate(actualLen > 8192 ? 8192 : actualLen);
        Preview p = Adaptable.Factory.getAdapter(sink, Preview.class, null);
        if (p != null) {
          progressSupport.start(2 * da.length());
          while (!da.isEmpty()) {
            checkCancelled();
            da.readBytes(buf);
            p.preview(buf);
            buf.clear();
          }
          da.reset();
          prepare(revisionNumber, da);
          progressSupport.worked(da.length());
          buf.clear();
        } else {
          progressSupport.start(da.length());
        }
        while (!da.isEmpty()) {
          checkCancelled();
          da.readBytes(buf);
          buf.flip(); // post: position == 0
          // XXX I may not rely on returned number of bytes but track change in buf position instead.
         
          int consumed = sink.write(buf);
          if ((consumed == 0 || consumed != buf.position()) && logFacility != null) {
            logFacility.dump(getClass(), Warn, "Bad data sink when reading revision %d. Reported %d bytes consumed, byt actually read %d", revisionNumber, consumed, buf.position());
          }
          if (buf.position() == 0) {
            throw new HgInvalidStateException("Bad sink implementation (consumes no bytes) results in endless loop");
          }
          buf.compact(); // ensure (a) there's space for new (b) data starts at 0
          progressSupport.worked(consumed);
        }
        progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully.
      } catch (IOException ex) {
        recordFailure(ex);
      } catch (CancelledException ex) {
        recordFailure(ex);
      }
    }
  }


  private static final class LinkRevDelegate extends RevlogDelegate {
    private final LinkRevisionInspector insp;

    public LinkRevDelegate(LinkRevisionInspector inspector, RevlogDelegate next) {
      super(next);
      assert inspector != null;
      insp = inspector;
    }
    @Override
    public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgRuntimeException {
      insp.next(revisionIndex, linkRevision);
      super.next(revisionIndex, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, data);
    }
  }
 
  private static final class RevisionDelegate extends RevlogDelegate {
    private final RevisionInspector insp;
    public RevisionDelegate(RevisionInspector inspector, RevlogDelegate next) {
      super(next);
      assert inspector != null;
      insp = inspector;
    }
    @Override
    public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgRuntimeException {
      Nodeid nid = getRevision(nodeid);
      insp.next(revisionIndex, nid, linkRevision);
      super.next(revisionIndex, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, data);
    }
  }
 
  private static class ParentDelegate extends RevlogDelegate {
    private final ParentInspector insp;
    private int i = 0;
    private final Nodeid[] allRevisions;
    // next are to build set of parent indexes that are not part of the range iteration
    // i.e. those parents we need to read separately. See Issue 31 for details.
    private final int[]      firstParentIndexes;
    private final int[]     secondParentIndexes;
    private final IntMap<Nodeid> missingParents;
    private final int start;

    public ParentDelegate(ParentInspector inspector, RevlogDelegate next, int _start, int end) {
      super(next);
      insp = inspector;
      start = _start;
      allRevisions = new Nodeid[end - _start + 1];
      firstParentIndexes = _start == 0 ? null : new int[allRevisions.length];
      secondParentIndexes = _start == 0 ? null : new int[allRevisions.length];
      missingParents = _start == 0 ? null : new IntMap<Nodeid>(16);
    }
    @Override
    public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1RevIndex, int parent2RevIndex, byte[] nodeid, DataAccess data) throws HgRuntimeException {
      allRevisions[i] = getRevision(nodeid);
      if (start > 0) {
        // there are chances we don't know parents here,
        // postpone parent dispatching for later, now just collect what's missing
        firstParentIndexes[i] = parent1RevIndex;
        secondParentIndexes[i] = parent2RevIndex;
        if (parent1RevIndex < start && parent1RevIndex >= 0) {
          missingParents.put(parent1RevIndex, null);
        }
        if (parent2RevIndex < start && parent2RevIndex >= 0) {
          missingParents.put(parent2RevIndex, null);
        }
      } else {
        // we iterate from the very beginning, got every index we'll need
        Nodeid p1 = parent1RevIndex == -1 ? Nodeid.NULL : allRevisions[parent1RevIndex];
        Nodeid p2 = parent2RevIndex == -1 ? Nodeid.NULL : allRevisions[parent2RevIndex];
        insp.next(revisionIndex, allRevisions[i], parent1RevIndex, parent2RevIndex, p1, p2);
      }
      i++;
      super.next(revisionIndex, actualLen, baseRevision, linkRevision, parent1RevIndex, parent2RevIndex, nodeid, data);
    }

    @Override
    protected void postWalk(HgRepository hgRepo) {
      if (start > 0) {
        complete(hgRepo);
      }
      super.postWalk(hgRepo);
    }

    private void complete(HgRepository repo) {
      if (missingParents.size() > 0) {
        // it's possible to get empty missingParents when _start > 0 e.g. when n-th file revision
        // is a copy of another file and hence got -1,-1 parents in this revlog, and we indexWalk(n,n)
        for (int k = missingParents.firstKey(), l = missingParents.lastKey(); k <= l; k++) {
          // TODO [post-1.1] int[] IntMap#keys() or even sort of iterator that can modify values
          if (missingParents.containsKey(k)) {
            Nodeid nid = repo.getChangelog().getRevision(k);
            missingParents.put(k, nid);
          }
        }
      }

      for (int i = 0, revNum = start; i < allRevisions.length; i++, revNum++) {
        int riP1 = firstParentIndexes[i];
        int riP2 = secondParentIndexes[i];
        Nodeid p1, p2;
        p1 = p2 = Nodeid.NULL;
        if (riP1 >= start) {
          // p1 of revNum's revision is out of iterated range
          // (don't check for riP1<end as I assume parents come prior to children in the changelog)
          p1 = allRevisions[riP1 - start];
        } else if (riP1 != -1) {
          assert riP1 >=0 && riP1 < start;
          p1 = missingParents.get(riP1);
          assert p1 != null;
        }
        // same for Pp2
        if (riP2 >= start) {
          p2 = allRevisions[riP2 - start];
        } else if (riP2 != -1) {
          assert riP2 >= 0 && riP2 < start;
          p2 = missingParents.get(riP2);
          assert p2 != null;
        }
        insp.next(revNum, allRevisions[i], riP1, riP2, p1, p2);
      }
    }
  }
}
TOP

Related Classes of org.tmatesoft.hg.repo.Revlog$ParentDelegate

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.