Package org.tmatesoft.hg.repo

Source Code of org.tmatesoft.hg.repo.HgMergeState$Entry

/*
* Copyright (c) 2011-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.core.Nodeid.NULL;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.tmatesoft.hg.core.HgFileRevision;
import org.tmatesoft.hg.core.HgIOException;
import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.internal.Internals;
import org.tmatesoft.hg.internal.LineReader;
import org.tmatesoft.hg.internal.ManifestRevision;
import org.tmatesoft.hg.internal.Pool;
import org.tmatesoft.hg.util.LogFacility.Severity;
import org.tmatesoft.hg.util.Pair;
import org.tmatesoft.hg.util.Path;
import org.tmatesoft.hg.util.PathRewrite;

/**
* Access to repository's merge state
*
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
public class HgMergeState {
  private Nodeid wcp1, wcp2, stateParent;
 
  public enum Kind {
    Resolved, Unresolved;
  }
 
  public static class Entry {
    private final Kind state;
    private final HgFileRevision parent1;
    private final HgFileRevision parent2;
    private final HgFileRevision ancestor;
    private final Path wcFile;

    /*package-local*/Entry(Kind s, Path actualCopy, HgFileRevision p1, HgFileRevision p2, HgFileRevision ca) {
      if (p1 == null || p2 == null || ca == null || actualCopy == null) {
        throw new IllegalArgumentException();
      }
      state = s;
      wcFile = actualCopy;
      parent1 = p1;
      parent2 = p2;
      ancestor = ca;
    }
   
    public Kind getState() {
      return state;
    }
    public Path getActualFile() {
      return wcFile;
    }
    public HgFileRevision getFirstParent() {
      return parent1;
    }
    public HgFileRevision getSecondParent() {
      return parent2;
    }
    public HgFileRevision getCommonAncestor() {
      return ancestor;
    }
  }

  private final Internals repo;
  private Entry[] entries;

  HgMergeState(Internals internalRepo) {
    repo = internalRepo;
  }

  /**
   * Update our knowledge about repository's merge state
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public void refresh() throws HgRuntimeException {
    final HgRepository hgRepo = repo.getRepo();
    entries = null;
    // it's possible there are two parents but no merge/state, we shall report this case as 'merging', with proper
    // first and second parent values
    stateParent = Nodeid.NULL;
    Pool<Nodeid> nodeidPool = new Pool<Nodeid>();
    Pool<Path> fnamePool = new Pool<Path>();
    Pair<Nodeid, Nodeid> wcParents = hgRepo.getWorkingCopyParents();
    wcp1 = nodeidPool.unify(wcParents.first()); wcp2 = nodeidPool.unify(wcParents.second());
    final File f = repo.getRepositoryFile(HgRepositoryFiles.MergeState);
    if (!f.canRead()) {
      // empty state
      return;
    }
    try {
      ArrayList<Entry> result = new ArrayList<Entry>();
      // pipe (already normalized) names from mergestate through same pool of filenames as use manifest revisions 
      Path.Source pathPool = new Path.SimpleSource(new PathRewrite.Empty(), fnamePool);
      final ManifestRevision m1 = new ManifestRevision(nodeidPool, fnamePool);
      final ManifestRevision m2 = new ManifestRevision(nodeidPool, fnamePool);
      if (!wcp2.isNull()) {
        final int rp2 = hgRepo.getChangelog().getRevisionIndex(wcp2);
        hgRepo.getManifest().walk(rp2, rp2, m2);
      }
      LineReader lr = new LineReader(f, repo.getLog());
      Iterator<String> lines = lr.read(new LineReader.SimpleLineCollector(), new ArrayList<String>()).iterator();
      String s = lines.next();
      stateParent = nodeidPool.unify(Nodeid.fromAscii(s));
      final int rp1 = hgRepo.getChangelog().getRevisionIndex(stateParent);
      hgRepo.getManifest().walk(rp1, rp1, m1);
      while (lines.hasNext()) {
        s = lines.next();
        String[] r = s.split("\\00");
        if (r.length < 7) {
          repo.getLog().dump(getClass(), Severity.Error, "Expect at least 7 zero-separated fields in the merge state file, not %d. Entry skipped", r.length);
          continue;
        }
        Path p1fname = pathPool.path(r[3]);
        Nodeid nidP1 = m1.nodeid(p1fname);
        Nodeid nidCA = nodeidPool.unify(Nodeid.fromAscii(r[5]));
        HgFileRevision p1 = new HgFileRevision(hgRepo, nidP1, m1.flags(p1fname), p1fname);
        HgFileRevision ca;
        if (nidCA == nidP1 && r[3].equals(r[4])) {
          ca = p1;
        } else {
          ca = new HgFileRevision(hgRepo, nidCA, null, pathPool.path(r[4]));
        }
        HgFileRevision p2;
        if (!wcp2.isNull() || !r[6].equals(r[4])) {
          final Path p2fname = pathPool.path(r[6]);
          Nodeid nidP2 = m2.nodeid(p2fname);
          if (nidP2 == null) {
            assert false : "There's not enough information (or I don't know where to look) in merge/state to find out what's the second parent";
            nidP2 = NULL;
          }
          p2 = new HgFileRevision(hgRepo, nidP2, m2.flags(p2fname), p2fname);
        } else {
          // no second parent known. no idea what to do here, assume linear merge, use common ancestor as parent
          p2 = ca;
        }
        final Kind k;
        if ("u".equals(r[1])) {
          k = Kind.Unresolved;
        } else if ("r".equals(r[1])) {
          k = Kind.Resolved;
        } else {
          throw new HgInvalidStateException(String.format("Unknown merge kind %s", r[1]));
        }
        Entry e = new Entry(k, pathPool.path(r[0]), p1, p2, ca);
        result.add(e);
      }
      entries = result.toArray(new Entry[result.size()]);
    } catch (HgIOException ex) {
      throw new HgInvalidControlFileException("Merge state read failed", ex, f);
    }
  }

  /**
   * Repository is in 'merging' state when changeset to be committed got two parents.
   * This method doesn't tell whether there are (un)resolved conflicts in the working copy,
   * use {@link #getConflicts()} (which makes sense only when {@link #isStale()} is <code>false</code>).
   * @return <code>true</code> when repository is being merged
   */
  public boolean isMerging() {
    return !getFirstParent().isNull() && !getSecondParent().isNull() && !isStale();
  }
 
  /**
   * Merge state file may not match actual working copy due to rollback or undo operations.
   * Value of {@link #getConflicts()} is reasonable iff this method returned <code>false</code>.
   * 
   * @return <code>true</code> when recorded merge state doesn't seem to correspond to present working copy
   */
  public boolean isStale() {
    assert wcp1 != null;
    return !stateParent.isNull() /*there's merge state*/ && !wcp1.equals(stateParent) /*and it doesn't match*/;
  }

  /**
   * It's possible for a repository to be in a 'merging' state (@see {@link #isMerging()} without any
   * conflict to resolve (no merge state information file).
   *
   * @return first parent of the working copy, never <code>null</code>
   */
  public Nodeid getFirstParent() {
    assert wcp1 != null;
    return wcp1;
  }
 
  /**
   * @return second parent of the working copy, never <code>null</code>
   */
  public Nodeid getSecondParent() {
    assert wcp2 != null;
    return wcp2;
  }
 
  /**
   * @return revision of the merge state or {@link Nodeid#NULL} if there's no merge state
   */
  public Nodeid getStateParent() {
    assert stateParent != null;
    return stateParent;
  }

  /**
   * List of conflicts as recorded in the merge state information file.
   * Note, this information is not valid when {@link #isStale()} is <code>true</code>.
   *
   * @return non-<code>null</code> list with both resolved and unresolved conflicts.
   */
  public List<Entry> getConflicts() {
    return entries == null ? Collections.<Entry>emptyList() : Arrays.asList(entries);
  }
}
TOP

Related Classes of org.tmatesoft.hg.repo.HgMergeState$Entry

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.