Package org.eclipse.egit.core.synchronize

Source Code of org.eclipse.egit.core.synchronize.GitCommitsModelCache

/*******************************************************************************
* Copyright (C) 2011, 2013 Dariusz Luksza <dariusz@luksza.org> and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.egit.core.synchronize;

import static org.eclipse.jgit.lib.ObjectId.zeroId;
import static org.eclipse.jgit.treewalk.filter.TreeFilter.ANY_DIFF;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevFlagSet;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;

/**
* Retrieves list of commits and the changes associated with each commit
*/
public class GitCommitsModelCache {

  /**
   * Constant copied from org.eclipse.compare.structuremergeviewer.Differencer.ADDITION
   * in order to avoid UI dependencies introduced by the org.eclipse.compare bundle
   */
  public static final int ADDITION = 1;

  /**
   * Constant copied from org.eclipse.compare.structuremergeviewer.Differencer.DELETION
   * in order to avoid UI dependencies introduced by the org.eclipse.compare bundle
   */
  public static final int DELETION = 2;

  /**
   * Constant copied from org.eclipse.compare.structuremergeviewer.Differencer.CHANGE
   * in order to avoid UI dependencies introduced by the org.eclipse.compare bundle
   */
  public static final int CHANGE = 3;

  /**
   * Bit mask (value 3) for extracting the kind of difference.
   */
  private static final int CHANGE_TYPE_MASK = 3;

  /**
   * Constant copied from org.eclipse.compare.structuremergeviewer.Differencer.LEFT
   * in order to avoid UI dependencies introduced by the org.eclipse.compare bundle
   * <p>
   * Note that in the context of synchronization, this means "INCOMING", shown
   * by an arrow pointing from right to left.
   */
  public static final int LEFT = 4;

  /**
   * Constant copied from org.eclipse.compare.structuremergeviewer.Differencer.RIGHT
   * in order to avoid UI dependencies introduced by the org.eclipse.compare bundle
   * <p>
   * Note that in the context of synchronization, this means "OUTGOING", shown
   * by an arrow pointing from left to right.
   */
  public static final int RIGHT = 8;

  /**
   * Corresponds to {@link RevCommit} object, but contains only those data
   * that are required by Synchronize view Change Set
   */
  public static class Commit {
    private int direction;

    private String shortMessage;

    private AbbreviatedObjectId commitId;

    private Date commitDate;

    private String authorName;

    private String committerName;

    private Map<String, Change> children;

    private Commit() {
      // reduce the visibility of the default constructor
    }

    /**
     * Indicates if this commit is incoming or outgoing. Returned value
     * corresponds to org.eclipse.compare.structuremergeviewer.Differencer#LEFT for incoming and
     * org.eclipse.compare.structuremergeviewer.Differencer#RIGHT for outgoing changes
     *
     * @return change direction
     */
    public int getDirection() {
      return direction;
    }

    /**
     * @return commit id
     */
    public AbbreviatedObjectId getId() {
      return commitId;
    }

    /**
     * @return commit author
     */
    public String getAuthorName() {
      return authorName;
    }

    /**
     * @return the committer name
     */
    public String getCommitterName() {
      return committerName;
    }

    /**
     * @return commit date
     */
    public Date getCommitDate() {
      return commitDate;
    }

    /**
     * @return commit short message
     */
    public String getShortMessage() {
      return shortMessage;
    }

    /**
     * @return list of changes made by this commit or {@code null} when
     *         commit doesn't have any changes
     */
    public Map<String, Change> getChildren() {
      return children;
    }

    /**
     * Disposes nested resources
     */
    public void dispose() {
      children.clear();
    }

  }

  /**
   * Describes single tree or blob change in commit.
   */
  public static class Change {
    int kind;

    String name;

    AbbreviatedObjectId objectId;

    AbbreviatedObjectId commitId;

    AbbreviatedObjectId remoteCommitId;

    AbbreviatedObjectId remoteObjectId;

    Change() {
      // reduce the visibility of the default constructor
    }

    /**
     * Describes if this change is incoming/outgoing addition, deletion or
     * change.
     *
     * It uses static values of LEFT, RIGHT, ADDITION, DELETION, CHANGE from
     * org.eclipse.compare.structuremergeviewer.Differencer class.
     *
     * @return kind
     */
    public int getKind() {
      return kind;
    }

    /**
     * @return object name
     */
    public String getName() {
      return name;
    }

    /**
     * @return id of commit containing this change
     */
    public AbbreviatedObjectId getCommitId() {
      return commitId;
    }

    /**
     * @return id of parent commit
     */
    public AbbreviatedObjectId getRemoteCommitId() {
      return remoteCommitId;
    }

    /**
     * @return object id
     */
    public AbbreviatedObjectId getObjectId() {
      return objectId;
    }

    /**
     * @return remote object id
     */
    public AbbreviatedObjectId getRemoteObjectId() {
      return remoteObjectId;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result
          + ((objectId == null) ? 0 : objectId.hashCode());
      result = prime
          * result
          + ((remoteObjectId == null) ? 0 : remoteObjectId.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      Change other = (Change) obj;
      if (objectId == null) {
        if (other.objectId != null)
          return false;
      } else if (!objectId.equals(other.objectId))
        return false;
      if (remoteObjectId == null) {
        if (other.remoteObjectId != null)
          return false;
      } else if (!remoteObjectId.equals(other.remoteObjectId))
        return false;
      return true;
    }

    @Override
    public String toString() {
      StringBuilder change = new StringBuilder("Change("); //$NON-NLS-1$
      if ((kind & LEFT) != 0)
        change.append("INCOMING "); //$NON-NLS-1$
      else
        // should be RIGHT
        change.append("OUTGOING "); //$NON-NLS-1$
      int changeType = kind & CHANGE_TYPE_MASK;
      if (changeType == CHANGE)
        change.append("CHANGE "); //$NON-NLS-1$
      else if (changeType == ADDITION)
        change.append("ADDITION "); //$NON-NLS-1$
      else if (changeType == DELETION)
        change.append("DELETION "); //$NON-NLS-1$

      change.append(name);
      change.append(";\n\tcurrent objectId: "); //$NON-NLS-1$
      change.append(getObjectId(objectId));
      change.append(";\n\tparent objectId: "); //$NON-NLS-1$
      change.append(getObjectId(remoteObjectId));
      change.append(";\n\tcurrent commit: "); //$NON-NLS-1$
      change.append(getObjectId(commitId));
      change.append(";\n\tparent commit: "); //$NON-NLS-1$
      change.append(getObjectId(remoteCommitId));
      change.append("\n)"); //$NON-NLS-1$

      return change.toString();
    }

    private String getObjectId(AbbreviatedObjectId object) {
      if (object != null)
        return object.toObjectId().getName();
      else
        return ObjectId.zeroId().getName();
    }

  }

  static final AbbreviatedObjectId ZERO_ID = AbbreviatedObjectId
      .fromObjectId(zeroId());

  /**
   * Scans given {@code repo} and build list of commits between two given
   * RevCommit objectId's. Each commit contains list of changed resources
   *
   * @param repo
   *            repository that should be scanned
   * @param srcId
   *            commit id that is considered the "local" version (e.g. from
   *            master)
   * @param dstId
   *            commit id that is considered the "remote" version (e.g. from
   *            origin/master)
   * @param pathFilter
   *            path filter definition or {@code null} when all paths should
   *            be included
   * @return list of {@link Commit} object's between {@code srcId} and
   *         {@code dstId}
   * @throws IOException
   */
  public static List<Commit> build(Repository repo, ObjectId srcId,
      ObjectId dstId, TreeFilter pathFilter) throws IOException {
    if (dstId.equals(srcId))
      return new ArrayList<Commit>(0);

    final RevWalk rw = new RevWalk(repo);

    final RevFlag localFlag = rw.newFlag("local"); //$NON-NLS-1$
    final RevFlag remoteFlag = rw.newFlag("remote"); //$NON-NLS-1$
    final RevFlagSet allFlags = new RevFlagSet();
    allFlags.add(localFlag);
    allFlags.add(remoteFlag);
    rw.carry(allFlags);

    RevCommit srcCommit = rw.parseCommit(srcId);
    srcCommit.add(localFlag);
    rw.markStart(srcCommit);
    srcCommit = null; // free not needed resources

    RevCommit dstCommit = rw.parseCommit(dstId);
    dstCommit.add(remoteFlag);
    rw.markStart(dstCommit);
    dstCommit = null; // free not needed resources

    if (pathFilter != null)
      rw.setTreeFilter(pathFilter);

    List<Commit> result = new ArrayList<Commit>();
    for (RevCommit revCommit : rw) {
      if (revCommit.hasAll(allFlags))
        break;

      Commit commit = new Commit();
      commit.shortMessage = revCommit.getShortMessage();
      commit.commitId = AbbreviatedObjectId.fromObjectId(revCommit);
      commit.authorName = revCommit.getAuthorIdent().getName();
      commit.committerName = revCommit.getCommitterIdent().getName();
      commit.commitDate = revCommit.getAuthorIdent().getWhen();

      RevCommit parentCommit = getParentCommit(revCommit);
      if (revCommit.has(localFlag))
        // Outgoing
        commit.direction = RIGHT;
      else if (revCommit.has(remoteFlag))
        // Incoming
        commit.direction = LEFT;
      else
        throw new GitCommitsModelDirectionException();

      commit.children = getChangedObjects(repo, revCommit,
          parentCommit, pathFilter, commit.direction);

      if (commit.children != null)
        result.add(commit);
    }
    rw.dispose();

    return result;
  }

  private static RevCommit getParentCommit(RevCommit commit) {
    if (commit.getParents().length > 0)
      return commit.getParents()[0];
    else
      return null;
  }

  private static Map<String, Change> getChangedObjects(Repository repo,
      RevCommit commit, RevCommit parentCommit,
      TreeFilter pathFilter, final int direction) throws IOException {
    final TreeWalk tw = new TreeWalk(repo);
    int commitIndex = addTree(tw, commit);
    int parentCommitIndex = addTree(tw, parentCommit);

    tw.setRecursive(true);
    if (pathFilter == null)
      tw.setFilter(ANY_DIFF);
    else
      tw.setFilter(AndTreeFilter.create(ANY_DIFF, pathFilter));

    final Map<String, Change> result = new HashMap<String, GitCommitsModelCache.Change>();
    final AbbreviatedObjectId commitId = getAbbreviatedObjectId(commit);
    final AbbreviatedObjectId parentCommitId = getAbbreviatedObjectId(parentCommit);

    MutableObjectId idBuf = new MutableObjectId();
    while (tw.next()) {
      Change change = new Change();
      change.commitId = commitId;
      change.remoteCommitId = parentCommitId;
      change.name = tw.getNameString();
      tw.getObjectId(idBuf, commitIndex);
      change.objectId = AbbreviatedObjectId.fromObjectId(idBuf);
      tw.getObjectId(idBuf, parentCommitIndex);
      change.remoteObjectId = AbbreviatedObjectId.fromObjectId(idBuf);

      calculateAndSetChangeKind(direction, change);

      result.put(tw.getPathString(), change);
    }
    tw.release();

    return result.size() > 0 ? result : null;
  }

  private static int addTree(TreeWalk tw, RevCommit commit)
      throws IOException {
    if (commit != null)
      return tw.addTree(commit.getTree());
    else
      return tw.addTree(new EmptyTreeIterator());
  }

  private static AbbreviatedObjectId getAbbreviatedObjectId(RevCommit commit) {
    if (commit != null)
      return AbbreviatedObjectId.fromObjectId(commit);
    else
      return ZERO_ID;
  }

  static void calculateAndSetChangeKind(final int direction, Change change) {
    if (ZERO_ID.equals(change.objectId)) { // removed in commit
      change.objectId = null; // clear zero id;
      change.kind = direction | DELETION;
    } else if (ZERO_ID.equals(change.remoteObjectId)) { // added in commit
      change.remoteObjectId = null; // clear zero id;
      change.kind = direction | ADDITION;
    } else
      change.kind = direction | CHANGE;
  }

}
TOP

Related Classes of org.eclipse.egit.core.synchronize.GitCommitsModelCache

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.