<commit_before>/*
 * Copyright (C) 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.copybara.git;

import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.copybara.ChangeMessage;
import com.google.copybara.GeneralOptions;
import com.google.copybara.LabelFinder;
import com.google.copybara.TransformResult;
import com.google.copybara.exception.RepoException;
import com.google.copybara.exception.ValidationException;
import com.google.copybara.git.GitDestination.MessageInfo;
import com.google.copybara.git.GitRepository.GitLogEntry;
import com.google.copybara.git.GitRepository.StatusCode;
import com.google.copybara.git.GitRepository.StatusFile;
import com.google.copybara.profiler.Profiler.ProfilerTask;
import com.google.copybara.util.DiffUtil;
import com.google.copybara.util.DirFactory;
import com.google.copybara.util.console.Console;

import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.eval.StarlarkValue;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;

/** Integrate changes from a url present in the migrated change label. */
@StarlarkBuiltin(
    name = "git_integrate",
    documented = false,
    doc = "")
public class GitIntegrateChanges implements StarlarkValue {

  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final String label;
  private final Strategy strategy;
  private final boolean ignoreErrors;

  GitIntegrateChanges(String label, Strategy strategy, boolean ignoreErrors) {
    this.label = Preconditions.checkNotNull(label);
    this.strategy = Preconditions.checkNotNull(strategy);
    this.ignoreErrors = ignoreErrors;
  }

  /**
   * Perform an integrate of changes for matching labels in the existing {@code repository} HEAD.
   *
   * @throws CannotIntegrateException if a change cannot be integrated due to a user error
   * @throws RepoException if a git related error happens during the integrate
   */
  void run(GitRepository repository, GeneralOptions generalOptions,
      MessageInfo messageInfo, Predicate<String> externalFileMatcher, TransformResult result,
      boolean ignoreIntegrationErrors) throws CannotIntegrateException, RepoException {
    try {
      doIntegrate(repository, generalOptions, externalFileMatcher, result, messageInfo);
    } catch (CannotIntegrateException e) {
      if (ignoreIntegrationErrors || ignoreErrors) {
        logger.atWarning().withCause(e).log("Cannot integrate changes");
        generalOptions.console().warnFmt("Cannot integrate changes: %s", e.getMessage());
      } else {
        throw e;
      }
    } catch (RepoException e) {
      if (ignoreIntegrationErrors || ignoreErrors) {
        logger.atSevere().withCause(e).log("Cannot integrate changes");
        generalOptions.console().warnFmt("Cannot integrate changes: %s", e.getMessage());
      } else {
        throw e;
      }
    }
  }

  private void doIntegrate(GitRepository repository, GeneralOptions generalOptions,
      Predicate<String> externalFiles, TransformResult result, MessageInfo messageInfo)
      throws CannotIntegrateException, RepoException {

    for (LabelFinder label : result.findAllLabels()) {
      if (!label.isLabel() || !this.label.equals(label.getName())) {
        continue;
      }
      if (label.getValue().isEmpty()) {
        throw new CannotIntegrateException("Found an empty value for label " + this.label);
      }
      try (ProfilerTask ignore = generalOptions.profiler().start("integrate",
          ImmutableMap.of("URL", label.getValue()))) {
        generalOptions.console().progressFmt("Integrating change from '%s'", label.getValue());
        IntegrateLabel integrateLabel = GitHubPrIntegrateLabel.parse(label.getValue(), repository,
            generalOptions);
        if (integrateLabel == null) {
          integrateLabel = GerritIntegrateLabel.parse(label.getValue(), repository,
              generalOptions);
          if (integrateLabel == null) {
            GitRevision gitRevision =
                GitRepoType.GIT.resolveRef(repository, /*repoUrl=*/null, label.getValue(),
                    generalOptions, /*describeVersion=*/false, /*partialFetch*/ false);
            integrateLabel = IntegrateLabel.genericGitRevision(gitRevision);
          }
        }

        strategy.integrate(repository, integrateLabel, externalFiles, label,
            messageInfo, generalOptions.console(), generalOptions.getDirFactory());
      } catch (ValidationException e) {
        throw new CannotIntegrateException("Error resolving " + label.getValue(), e);
      }
    }
  }

  /**
   * What should we do when we find a change to be integrated
   */
  public enum Strategy {
    /**
     * A simple git fake-merge: Ignore any content from the change url.
     */
    FAKE_MERGE {<commit_msg>Also print strategy used<commit_after>/*
 * Copyright (C) 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.copybara.git;

import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.copybara.ChangeMessage;
import com.google.copybara.GeneralOptions;
import com.google.copybara.LabelFinder;
import com.google.copybara.TransformResult;
import com.google.copybara.exception.RepoException;
import com.google.copybara.exception.ValidationException;
import com.google.copybara.git.GitDestination.MessageInfo;
import com.google.copybara.git.GitRepository.GitLogEntry;
import com.google.copybara.git.GitRepository.StatusCode;
import com.google.copybara.git.GitRepository.StatusFile;
import com.google.copybara.profiler.Profiler.ProfilerTask;
import com.google.copybara.util.DiffUtil;
import com.google.copybara.util.DirFactory;
import com.google.copybara.util.console.Console;

import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.eval.StarlarkValue;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;

/** Integrate changes from a url present in the migrated change label. */
@StarlarkBuiltin(
    name = "git_integrate",
    documented = false,
    doc = "")
public class GitIntegrateChanges implements StarlarkValue {

  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final String label;
  private final Strategy strategy;
  private final boolean ignoreErrors;

  GitIntegrateChanges(String label, Strategy strategy, boolean ignoreErrors) {
    this.label = Preconditions.checkNotNull(label);
    this.strategy = Preconditions.checkNotNull(strategy);
    this.ignoreErrors = ignoreErrors;
  }

  /**
   * Perform an integrate of changes for matching labels in the existing {@code repository} HEAD.
   *
   * @throws CannotIntegrateException if a change cannot be integrated due to a user error
   * @throws RepoException if a git related error happens during the integrate
   */
  void run(GitRepository repository, GeneralOptions generalOptions,
      MessageInfo messageInfo, Predicate<String> externalFileMatcher, TransformResult result,
      boolean ignoreIntegrationErrors) throws CannotIntegrateException, RepoException {
    try {
      doIntegrate(repository, generalOptions, externalFileMatcher, result, messageInfo);
    } catch (CannotIntegrateException e) {
      if (ignoreIntegrationErrors || ignoreErrors) {
        logger.atWarning().withCause(e).log("Cannot integrate changes");
        generalOptions.console().warnFmt("Cannot integrate changes: %s", e.getMessage());
      } else {
        throw e;
      }
    } catch (RepoException e) {
      if (ignoreIntegrationErrors || ignoreErrors) {
        logger.atSevere().withCause(e).log("Cannot integrate changes");
        generalOptions.console().warnFmt("Cannot integrate changes: %s", e.getMessage());
      } else {
        throw e;
      }
    }
  }

  private void doIntegrate(GitRepository repository, GeneralOptions generalOptions,
      Predicate<String> externalFiles, TransformResult result, MessageInfo messageInfo)
      throws CannotIntegrateException, RepoException {

    for (LabelFinder label : result.findAllLabels()) {
      if (!label.isLabel() || !this.label.equals(label.getName())) {
        continue;
      }
      if (label.getValue().isEmpty()) {
        throw new CannotIntegrateException("Found an empty value for label " + this.label);
      }
      try (ProfilerTask ignore = generalOptions.profiler().start("integrate",
          ImmutableMap.of("URL", label.getValue()))) {
        generalOptions.console().progressFmt("Integrating change from '%s' using strategy %s",
            label.getValue(), strategy);
        IntegrateLabel integrateLabel = GitHubPrIntegrateLabel.parse(label.getValue(), repository,
            generalOptions);
        if (integrateLabel == null) {
          integrateLabel = GerritIntegrateLabel.parse(label.getValue(), repository,
              generalOptions);
          if (integrateLabel == null) {
            GitRevision gitRevision =
                GitRepoType.GIT.resolveRef(repository, /*repoUrl=*/null, label.getValue(),
                    generalOptions, /*describeVersion=*/false, /*partialFetch*/ false);
            integrateLabel = IntegrateLabel.genericGitRevision(gitRevision);
          }
        }

        strategy.integrate(repository, integrateLabel, externalFiles, label,
            messageInfo, generalOptions.console(), generalOptions.getDirFactory());
      } catch (ValidationException e) {
        throw new CannotIntegrateException("Error resolving " + label.getValue(), e);
      }
    }
  }

  /**
   * What should we do when we find a change to be integrated
   */
  public enum Strategy {
    /**
     * A simple git fake-merge: Ignore any content from the change url.
     */
    FAKE_MERGE {
{'fraction_non_alphanumeric': 0.09739524348810873, 'fraction_numerical': 0.00139384963847025, 'mean_word_length': 3.8316498316498318, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3517104', 'n_tokens_mistral': 3078, 'n_tokens_neox': 3015, 'n_words': 817}