Package com.google.devtools.moe.client.logic

Source Code of com.google.devtools.moe.client.logic.BookkeepingLogic

// Copyright 2011 The MOE Authors All Rights Reserved.

package com.google.devtools.moe.client.logic;

import com.google.common.base.Joiner;
import com.google.devtools.moe.client.AppContext;
import com.google.devtools.moe.client.MoeProblem;
import com.google.devtools.moe.client.Ui;
import com.google.devtools.moe.client.codebase.Codebase;
import com.google.devtools.moe.client.codebase.CodebaseCreationError;
import com.google.devtools.moe.client.database.Db;
import com.google.devtools.moe.client.database.Equivalence;
import com.google.devtools.moe.client.database.EquivalenceMatcher;
import com.google.devtools.moe.client.database.EquivalenceMatcher.EquivalenceMatchResult;
import com.google.devtools.moe.client.database.SubmittedMigration;
import com.google.devtools.moe.client.migrations.MigrationConfig;
import com.google.devtools.moe.client.parser.Expression;
import com.google.devtools.moe.client.parser.RepositoryExpression;
import com.google.devtools.moe.client.project.InvalidProject;
import com.google.devtools.moe.client.project.ProjectContext;
import com.google.devtools.moe.client.project.TranslatorConfig;
import com.google.devtools.moe.client.repositories.Revision;
import com.google.devtools.moe.client.repositories.RevisionHistory;
import com.google.devtools.moe.client.repositories.RevisionMetadata;
import com.google.devtools.moe.client.tools.CodebaseDifference;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

/**
* Logic behind keeping MOE db up to date (moe bookkeeping)
*
*/
public class BookkeepingLogic {

  /** The regex for MOE-migrated changes, as found in the changelog of the to-repo. */
  private static final Pattern MIGRATED_REV_PATTERN = Pattern.compile("MOE_MIGRATED_REVID=(\\S*)");

  /**
   * Diff codebases at HEADs of fromRepository and toRepository, adding an Equivalence to db if
   * equivalent at HEADs.
   */
  private static void updateHeadEquivalence(String fromRepository, String toRepository,
                                            Db db, ProjectContext context) {
    Codebase to, from;
    try {
      to = new RepositoryExpression(toRepository).createCodebase(context);
      from = new RepositoryExpression(fromRepository)
          .translateTo(to.getProjectSpace())
          .createCodebase(context);
    } catch (CodebaseCreationError e) {
      AppContext.RUN.ui.error(e, "Could not generate codebase");
      return;
    }

    Ui.Task t = AppContext.RUN.ui.pushTask(
        "diff_codebases",
        String.format("Diff codebases '%s' and '%s'", from.toString(), to.toString()));
    if (!CodebaseDifference.diffCodebases(from, to).areDifferent()) {
      RevisionHistory fromHistory = context.repositories.get(fromRepository).revisionHistory;
      RevisionHistory toHistory = context.repositories.get(toRepository).revisionHistory;

      // TODO(user): Pull highest revision from the created codebases, not over again (in case
      // head has moved forward meanwhile).
      db.noteEquivalence(new Equivalence(fromHistory.findHighestRevision(null),
                                         toHistory.findHighestRevision(null)));
    }
    AppContext.RUN.ui.popTask(t, "");
  }

  /**
   * Find Revisions in toRepository that were the result of a migration, and call
   * processMigration() on each.
   */
  private static void updateCompletedMigrations(
      String fromRepository, String toRepository, Db db, ProjectContext context, boolean inverse) {

    RevisionHistory toHistory = context.repositories.get(toRepository).revisionHistory;
    EquivalenceMatchResult equivMatch = toHistory.findRevisions(
        null /*revision*/,
        new EquivalenceMatcher(fromRepository, db));

    List<Revision> linearToRevs = equivMatch.getRevisionsSinceEquivalence().getLinearHistory();
    AppContext.RUN.ui.info(String.format(
        "Found %d revisions in %s since equivalence (%s): %s",
        linearToRevs.size(),
        toRepository,
        equivMatch.getEquivalences(),
        Joiner.on(", ").join(linearToRevs)));

    for (Revision toRev : linearToRevs) {
      String fromRevId = getMigratedRevId(toHistory.getMetadata(toRev));
      if (fromRevId != null) {
        processMigration(new Revision(fromRevId, fromRepository), toRev, db, context, inverse);
      }
    }
  }

  private static @Nullable String getMigratedRevId(RevisionMetadata metadata) {
    Matcher migratedRevMatcher = MIGRATED_REV_PATTERN.matcher(metadata.description);
    return migratedRevMatcher.find() ? migratedRevMatcher.group(1) : null;
  }

  /**
   * Check a submitted migration for equivalence by translating the from-repo to the to-repo, or
   * in the case of an inverse translation, translating the to-repo to the from-repo via the
   * forward-translator.
   */
  private static void processMigration(Revision fromRev, Revision toRev,
                                       Db db, ProjectContext context, boolean inverse) {
    SubmittedMigration migration = new SubmittedMigration(fromRev, toRev);
    if (!db.noteMigration(migration)) {
      AppContext.RUN.ui.info("Skipping bookkeeping of this SubmittedMigration "
          + "because it was already in the Db: " + migration);
      return;
    }

    Codebase to, from;
    try {
      Expression toEx =
          new RepositoryExpression(toRev.repositoryName).atRevision(toRev.revId);
      Expression fromEx =
          new RepositoryExpression(fromRev.repositoryName).atRevision(fromRev.revId);

      // Use the forward-translator to check an inverse-translated migration.
      if (inverse) {
        String fromProjectSpace =
            context.config.getRepositoryConfigs().get(fromRev.repositoryName).getProjectSpace();
        toEx = toEx.translateTo(fromProjectSpace);
      } else {
        String toProjectSpace =
            context.config.getRepositoryConfigs().get(toRev.repositoryName).getProjectSpace();
        fromEx = fromEx.translateTo(toProjectSpace);
      }

      to = toEx.createCodebase(context);
      from = fromEx.createCodebase(context);

    } catch (CodebaseCreationError e) {
      AppContext.RUN.ui.error(e, "Could not generate codebase");
      return;
    } catch (InvalidProject e) {
      AppContext.RUN.ui.error("Project configuration error: " + e);
      return;
    }

    Ui.Task t = AppContext.RUN.ui.pushTask(
        "diff_codebases",
        String.format("Diff codebases '%s' and '%s'", from.toString(), to.toString()));
    if (!CodebaseDifference.diffCodebases(from, to).areDifferent()) {
      Equivalence newEquiv = new Equivalence(fromRev, toRev);
      db.noteEquivalence(newEquiv);
      AppContext.RUN.ui.info("Codebases are identical, noted new equivalence: " + newEquiv);
    }
    AppContext.RUN.ui.popTask(t, "");
  }

  /**
   * Look up the TranslatorConfig for translation of fromRepo to toRepo in the ProjectContext.
   */
  private static TranslatorConfig getTranslatorConfig(
      String fromRepo, String toRepo, ProjectContext context) {
    try {
      String fromProjectSpace =
          context.config.getRepositoryConfigs().get(fromRepo).getProjectSpace();
      String toProjectSpace = context.config.getRepositoryConfigs().get(toRepo).getProjectSpace();
      List<TranslatorConfig> transConfigs = context.config.getTranslators();
      for (TranslatorConfig transConfig : transConfigs) {
        if (transConfig.getFromProjectSpace().equals(fromProjectSpace)
            && transConfig.getToProjectSpace().equals(toProjectSpace)) {
          return transConfig;
        }
      }
      throw new InvalidProject("Couldn't find translator!");
    } catch (InvalidProject e) {
      throw new MoeProblem("Error getting translations for migration: " + e);
    }
  }

  /**
   * Looks for and adds to db SubmittedMigrations and Equivalences as the result of running one of
   * the directives Migrate or OneMigration, and the user commiting the result. Bookkeep only
   * considers Equivalences between repositories which are part of a migration listed both in
   * migrationNames and context.
   *
   * Bookkeep should be run before performing any directive which reads from the db, since it is
   * MOE's way of keeping the db up-to-date.
   *
   * @param migrationNames the names of all migrations
   * @param db  the database to update
   * @param dbLocation  where db is located
   * @param context the ProjectContext to evaluate in
   * @return  0 on success, 1 on failure
   */
  public static int bookkeep(List<String> migrationNames, Db db, String dbLocation,
                             ProjectContext context) {
    Ui.Task t = AppContext.RUN.ui.pushTask("perform_checks", "Updating database");
    for (String s : migrationNames) {
      MigrationConfig m = context.migrationConfigs.get(s);
      if (m == null) {
        AppContext.RUN.ui.error(String.format("No migration '%s' in MOE config", s));
        return 1;
      }

      Ui.Task bookkeepOneMigrationTask = AppContext.RUN.ui.pushTask(
          "bookkeping_one_migration",
          String.format("Doing bookkeeping between '%s' and '%s' for migration '%s'",
                        m.getFromRepository(), m.getToRepository(), m.getName()));

      TranslatorConfig migrationTranslator =
          getTranslatorConfig(m.getFromRepository(), m.getToRepository(), context);

      // TODO(user): ? Switch the order of these two checks, so that we don't have to look back
      // through the history for irrelevant equivalences if there's one at head.
      Ui.Task checkMigrationsTask = AppContext.RUN.ui.pushTask(
          "check_migrations",
          String.format(
              "Checking completed migrations for new equivalence between '%s' and '%s'",
              m.getFromRepository(), m.getToRepository()));
      updateCompletedMigrations(
          m.getFromRepository(), m.getToRepository(), db, context,
          migrationTranslator.isInverse());
      AppContext.RUN.ui.popTask(checkMigrationsTask, "");

      // Skip head-equivalence checking for inverse translation -- assume it will be performed via
      // the forward-translated migration.
      if (!migrationTranslator.isInverse()) {
        Ui.Task checkHeadsTask = AppContext.RUN.ui.pushTask(
            "check_heads",
            String.format(
                "Checking head equivalence between '%s' and '%s'",
                m.getFromRepository(), m.getToRepository()));
        updateHeadEquivalence(m.getFromRepository(), m.getToRepository(), db, context);
        AppContext.RUN.ui.popTask(checkHeadsTask, "");
      }

      AppContext.RUN.ui.popTask(bookkeepOneMigrationTask, "");
    }
    AppContext.RUN.ui.popTask(t, "");
    db.writeToLocation(dbLocation);
    return 0;
  }
}
TOP

Related Classes of com.google.devtools.moe.client.logic.BookkeepingLogic

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.