Package com.google.devtools.moe.client.codebase

Source Code of com.google.devtools.moe.client.codebase.CodebaseMerger

// Copyright 2011 The MOE Authors All Rights Reserved.

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

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.devtools.moe.client.AppContext;
import com.google.devtools.moe.client.CommandRunner.CommandException;
import com.google.devtools.moe.client.FileSystem;
import com.google.devtools.moe.client.MoeProblem;
import com.google.devtools.moe.client.parser.RepositoryExpression;
import com.google.devtools.moe.client.parser.Term;
import com.google.devtools.moe.client.tools.FileDifference;

import java.io.File;
import java.io.IOException;
import java.util.Set;

/**
* Once constructed with three codebases, this class can call merge() which will merge the three
* codebases into one. A CodebaseMerger keeps track of the names of files that either merged
* successfully or conflicted.
*
*/
public class CodebaseMerger {

  private final Codebase originalCodebase, destinationCodebase, modifiedCodebase, mergedCodebase;
  private final Set<String> mergedFiles, failedToMergeFiles;

  public CodebaseMerger(Codebase originalCodebase, Codebase modifiedCodebase,
      Codebase destinationCodebase) {
    this.originalCodebase = originalCodebase;
    this.modifiedCodebase = modifiedCodebase;
    this.destinationCodebase = destinationCodebase;

    File mergedDir = AppContext.RUN.fileSystem.getTemporaryDirectory("merged_codebase_");
    RepositoryExpression mergedExpression = new RepositoryExpression(
        new Term("merged", ImmutableMap.<String, String>of()));
    this.mergedCodebase = new Codebase(mergedDir, "merged", mergedExpression);

    mergedFiles = Sets.newHashSet();
    failedToMergeFiles = Sets.newHashSet();
  }

  public Set<String> getMergedFiles() {
    return ImmutableSet.copyOf(mergedFiles);
  }

  public Set<String> getFailedToMergeFiles() {
    return ImmutableSet.copyOf(failedToMergeFiles);
  }

  /**
   * For each file in the union of the modified and destination codebases, run
   * generateMergedFile(...) and then report() the results.
   *
   * @return the merged Codebase
   */
  public Codebase merge() {
    Set<String>  filesToMerge = Sets.union(destinationCodebase.getRelativeFilenames(),
        modifiedCodebase.getRelativeFilenames());
    for (String filename : filesToMerge) {
      this.generateMergedFile(filename);
    }
    this.report();
    return mergedCodebase;
  }

  /**
   * Print the results of a merge to the UI.
   */
  public void report() {
    AppContext.RUN.ui.info(String.format("Merged codebase generated at: %s",
        mergedCodebase.getPath().getAbsolutePath()));
    AppContext.RUN.ui.info(String.format("%d files merged successfully\n%d files have merge "
        + "conflicts. Edit the following files to resolve conflicts:\n%s", mergedFiles.size(),
        failedToMergeFiles.size(), failedToMergeFiles.toString()));
  }

  private static boolean areDifferent(String filename, File x, File y) {
    return FileDifference.CONCRETE_FILE_DIFFER.diffFiles(filename, x, y).isDifferent();
  }

  /**
   * Copy the destFile into the merged codebase. This is where the output of merge will be
   * written to.
   */
  private File copyToMergedCodebase(String filename, File destFile) {
    FileSystem fs = AppContext.RUN.fileSystem;
    File mergedFile = mergedCodebase.getFile(filename);
    try {
      fs.makeDirsForFile(mergedFile);
      fs.copyFile(destFile, mergedFile);
      return mergedFile;
    } catch (IOException e) {
      throw new MoeProblem(e.getMessage());
    }
  }

  /**
   * Given a filename, this method finds the file with that name in each of the three codebases.
   * Using the UNIX merge(1) tool, those three files are merged and the result is placed in the
   * merged codebase. Any conflicts that occurred during merging will appear in the merged codebase
   * file for the user to resolve.
   *
   * In the case where the file specified by the given filename exists in the original codebase and
   * in either the modified codebase or the destination codebase (but not both) and if the file
   * is unchanged between those codebases, then a file in the merged codebase will NOT be created
   * and this method will return leaving the merged codebase unchanged.
   *
   * @param filename the name of the file to merge
   */
  public void generateMergedFile(String filename) {
    FileSystem fs = AppContext.RUN.fileSystem;

    File origFile = originalCodebase.getFile(filename);
    boolean origExists = fs.exists(origFile);

    File destFile = destinationCodebase.getFile(filename);
    boolean destExists = fs.exists(destFile);

    File modFile = modifiedCodebase.getFile(filename);
    boolean modExists = fs.exists(modFile);

    if (!destExists && !modExists) {
      // This should never be thrown since generateMergedFile(...) is only called on filesToMerge
      // from merge() which is the union of the files in the destination and modified codebases.
      throw new MoeProblem(
          String.format("%s doesn't exist in either %s nor %s. This should not be possible.",
          filename, destinationCodebase, modifiedCodebase));

    } else if (origExists && modExists && !destExists) {
      if (areDifferent(filename, origFile, modFile)) {
        // Proceed and merge in /dev/null, which should produce a merge conflict (incoming edit on
        // delete).
        destFile = new File("/dev/null");
      } else {
        // Defer to deletion in destination codebase.
        return;
      }

    } else if (origExists && !modExists && destExists) {
      // Blindly follow deletion of the original file by not copying it into the merged codebase.
      return;

    } else if (!origExists && !(modExists && destExists)) {
      // File exists only in modified or destination codebase, so just copy it over.
      File existingFile = (modExists ? modFile : destFile);
      copyToMergedCodebase(filename, existingFile);
      return;

    } else if (!origExists && modExists && destExists) {
      // Merge both new files (conflict expected).
      origFile = new File("/dev/null");
    }

    File mergedFile = copyToMergedCodebase(filename, destFile);

    String mergeOutput;
    try {
      // Merges the changes that lead from origFile to modFile into mergedFile (which is a copy
      // of destFile). After, mergedFile will have the combined changes of modFile and destFile.
      mergeOutput = AppContext.RUN.cmd.runCommand("merge", ImmutableList.of(
          mergedFile.getAbsolutePath(), origFile.getAbsolutePath(), modFile.getAbsolutePath()),
          this.mergedCodebase.getPath().getAbsolutePath());
      // Return status was 0 and the merge was successful. Note it.
      mergedFiles.add(mergedFile.getAbsolutePath().toString());
    } catch (CommandException e) {
      // If merge fails with exit status 1, then a conflict occurred. Make a note of the filepath.
      if (e.returnStatus == 1) {
        failedToMergeFiles.add(mergedFile.getAbsolutePath().toString());
      } else {
        throw new MoeProblem(
            String.format(
                "Merge returned with unexpected status %d when trying to run \"merge -p %s %s %s\"",
                e.returnStatus,
                destFile.getAbsolutePath(),
                origFile.getAbsolutePath(),
                modFile.getAbsolutePath()));
      }
    }
  }
}
TOP

Related Classes of com.google.devtools.moe.client.codebase.CodebaseMerger

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.