All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.bjurr.violations.comments.gitlab.lib.GitLabCommentsProvider Maven / Gradle / Ivy

The newest version!
package se.bjurr.violations.comments.gitlab.lib;

import static java.util.logging.Level.SEVERE;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.stream.IntStream;
import org.gitlab4j.api.Constants;
import org.gitlab4j.api.Constants.TokenType;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.ProxyClientConfig;
import org.gitlab4j.api.models.Diff;
import org.gitlab4j.api.models.DiffRef;
import org.gitlab4j.api.models.MergeRequest;
import org.gitlab4j.api.models.Note;
import org.gitlab4j.api.models.Position;
import org.gitlab4j.api.models.Project;
import se.bjurr.violations.comments.lib.CommentsProvider;
import se.bjurr.violations.comments.lib.model.ChangedFile;
import se.bjurr.violations.comments.lib.model.Comment;
import se.bjurr.violations.lib.ViolationsLogger;
import se.bjurr.violations.lib.util.PatchParserUtil;

public class GitLabCommentsProvider implements CommentsProvider {
  private static final String MASK = "HIDDEN";
  static final String START_TITLE = "WIP: (VIOLATIONS)";
  private final ViolationCommentsToGitLabApi api;
  private final ViolationsLogger violationsLogger;
  private final GitLabApi gitLabApi;
  private final Project project;
  private final MergeRequest mergeRequestChanges;
  private final MergeRequest mergeRequest;

  public GitLabCommentsProvider(
      final ViolationsLogger violationsLogger, final ViolationCommentsToGitLabApi api) {
    this(violationsLogger, api, initGitLabApi(violationsLogger, api));
  }

  protected GitLabCommentsProvider(
      final ViolationsLogger violationsLogger,
      final ViolationCommentsToGitLabApi api,
      final GitLabApi gitLabApi,
      final Project project,
      final MergeRequest mergeRequestChanges,
      final MergeRequest mergeRequest) {
    this.api = api;
    this.violationsLogger = violationsLogger;
    this.gitLabApi = gitLabApi;
    this.project = project;
    this.mergeRequestChanges = mergeRequestChanges;
    this.mergeRequest = mergeRequest;
  }

  private GitLabCommentsProvider(
      final ViolationsLogger violationsLogger,
      final ViolationCommentsToGitLabApi api,
      final GitLabApi gitLabApi) {
    this(
        violationsLogger,
        api,
        gitLabApi,
        initProject(api, gitLabApi),
        initMergeRequestChanges(api, gitLabApi),
        initMergeRequest(api, gitLabApi));
  }

  @SuppressFBWarnings({"NP_LOAD_OF_KNOWN_NULL_VALUE", "SIC_INNER_SHOULD_BE_STATIC_ANON"})
  private static GitLabApi initGitLabApi(
      final ViolationsLogger violationsLogger, final ViolationCommentsToGitLabApi api) {
    final String hostUrl = api.getHostUrl();
    final String apiToken = api.getApiToken();
    final Map proxyConfig = getProxyConfig(api);
    final TokenType tokenType = TokenType.valueOf(api.getTokenType().name());
    final String secretToken = null;
    final GitLabApi gitLabApi =
        new GitLabApi(hostUrl, tokenType, apiToken, secretToken, proxyConfig);
    gitLabApi.setIgnoreCertificateErrors(api.isIgnoreCertificateErrors());
    gitLabApi.withRequestResponseLogging(
        new Logger(GitLabCommentsProvider.class.getName(), null) {
          @Override
          public void log(final LogRecord record) {
            String masked =
                record
                    .getMessage() //
                    .replace(apiToken, MASK);
            if (api.findProxyPassword().isPresent()) {
              masked = masked.replace(api.findProxyPassword().get(), MASK);
            }
            violationsLogger.log(record.getLevel(), masked);
          }
        },
        Level.INFO);
    return gitLabApi;
  }

  private static Project initProject(
      final ViolationCommentsToGitLabApi api, final GitLabApi gitLabApi) {
    final String projectId = api.getProjectId();
    try {
      return gitLabApi.getProjectApi().getProject(projectId);
    } catch (final GitLabApiException e) {
      throw new RuntimeException("Could not get project " + projectId, e);
    }
  }

  private static MergeRequest initMergeRequest(
      final ViolationCommentsToGitLabApi api, final GitLabApi gitLabApi) {
    final String projectId = api.getProjectId();
    final Long mergeRequestId = api.getMergeRequestIid();
    try {
      // This will populate diff_refs,
      // https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr
      return gitLabApi.getMergeRequestApi().getMergeRequest(projectId, mergeRequestId);
    } catch (final Throwable e) {
      throw new RuntimeException("Could not get MR " + projectId + " " + mergeRequestId, e);
    }
  }

  private static MergeRequest initMergeRequestChanges(
      final ViolationCommentsToGitLabApi api, final GitLabApi gitLabApi) {
    final String projectId = api.getProjectId();
    final Long mergeRequestId = api.getMergeRequestIid();
    try {
      return gitLabApi.getMergeRequestApi().getMergeRequestChanges(projectId, mergeRequestId);
    } catch (final Throwable e) {
      throw new RuntimeException("Could not get MR " + projectId + " " + mergeRequestId, e);
    }
  }

  private static Map getProxyConfig(final ViolationCommentsToGitLabApi api) {
    if (api.findProxyServer().isPresent()) {
      if (!api.findProxyUser().isPresent() || !api.findProxyPassword().isPresent()) {
        return ProxyClientConfig.createProxyClientConfig(api.findProxyServer().get());
      }
      if (api.findProxyUser().isPresent() && api.findProxyPassword().isPresent()) {
        return ProxyClientConfig.createProxyClientConfig(
            api.findProxyServer().get(), api.findProxyUser().get(), api.findProxyPassword().get());
      }
    }
    return new HashMap();
  }

  @Override
  public void createComment(final String comment) {
    this.markMergeRequestAsWIP();
    try {
      this.gitLabApi
          .getNotesApi()
          .createMergeRequestNote(this.project.getId(), this.mergeRequestChanges.getIid(), comment);
    } catch (final Throwable e) {
      this.violationsLogger.log(SEVERE, "Could create comment " + comment, e);
    }
  }

  /**
   * Set the merge request as "Work in Progress" if configured to do so by the shouldSetWIP flag.
   */
  @SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE")
  private void markMergeRequestAsWIP() {
    if (!this.api.getShouldSetWIP()) {
      return;
    }

    final String currentTitle = this.mergeRequestChanges.getTitle();
    final Optional titleOpt = getTitleWithWipPrefix(currentTitle);
    if (!titleOpt.isPresent()) {
      // To avoid setting WIP again on new comments
      return;
    }
    final Long projectId = this.project.getId();
    final Long mergeRequestIid = this.mergeRequestChanges.getIid();
    final String targetBranch = null;
    final Long assigneeId = null;
    final String title = titleOpt.get();
    final String description = null;
    final Constants.StateEvent stateEvent = null;
    final String labels = null;
    final Long milestoneId = null;
    final Boolean removeSourceBranch = null;
    final Boolean squash = null;
    final Boolean discussionLocked = null;
    final Boolean allowCollaboration = null;
    try {
      this.mergeRequestChanges.setTitle(title);
      this.gitLabApi
          .getMergeRequestApi()
          .updateMergeRequest(
              projectId,
              mergeRequestIid,
              targetBranch,
              title,
              assigneeId,
              description,
              stateEvent,
              labels,
              milestoneId,
              removeSourceBranch,
              squash,
              discussionLocked,
              allowCollaboration);
    } catch (final Throwable e) {
      this.violationsLogger.log(SEVERE, e.getMessage(), e);
    }
  }

  static Optional getTitleWithWipPrefix(String currentTitle) {
    if (currentTitle.startsWith(START_TITLE)) {
      return Optional.empty();
    }
    if (currentTitle.startsWith("WIP:")) {
      currentTitle = currentTitle.substring(4);
    }
    if (currentTitle.startsWith("WIP")) {
      currentTitle = currentTitle.substring(3);
    }
    final String title = START_TITLE + " " + currentTitle.trim();
    return Optional.of(title.trim());
  }

  @Override
  @SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE")
  public void createSingleFileComment(
      final ChangedFile file, final Integer newLine, final String content) {
    this.markMergeRequestAsWIP();
    Long projectId = null;
    final DiffRef diffRefs = this.mergeRequest.getDiffRefs();
    Objects.requireNonNull(
        diffRefs,
        "diffRefs is null for MR with Iid "
            + this.mergeRequest.getIid()
            + " in projectId "
            + this.mergeRequest.getProjectId());
    Position position = null;
    try {
      projectId = this.project.getId();
      final String baseSha = diffRefs.getBaseSha();
      final String startSha = diffRefs.getStartSha();
      final String headSha = diffRefs.getHeadSha();
      final String patchString = file.getSpecifics().get(0);
      final String oldPath = file.getSpecifics().get(1);
      final String newPath = file.getSpecifics().get(2);
      final Integer oldLine =
          new PatchParserUtil(patchString) //
              .findOldLine(newLine) //
              .orElse(null);
      final Date date = null;
      final String positionHash = null;
      position = new Position();
      position.setPositionType(Position.PositionType.TEXT);
      position.setBaseSha(baseSha);
      position.setStartSha(startSha);
      position.setHeadSha(headSha);
      position.setNewLine(newLine);
      position.setNewPath(newPath);
      position.setOldLine(oldLine);
      position.setOldPath(oldPath);
      this.gitLabApi
          .getDiscussionsApi()
          .createMergeRequestDiscussion(
              projectId, this.mergeRequestChanges.getIid(), content, date, positionHash, position);
    } catch (final Throwable e) {
      final String lineSeparator = System.lineSeparator();
      this.violationsLogger.log(
          SEVERE,
          "Could not create diff discussion!"
              + lineSeparator
              + "ProjectID: "
              + projectId
              + lineSeparator
              + "Violation: "
              + content
              + lineSeparator
              + ", position "
              + position,
          e);
    }
  }

  @Override
  public List getComments() {
    final List found = new ArrayList<>();
    try {

      final List notes =
          this.gitLabApi
              .getNotesApi()
              .getMergeRequestNotes(this.project.getId(), this.mergeRequestChanges.getIid());

      for (final Note note : notes) {
        final String identifier = note.getId().toString();
        final String content = note.getBody();
        final String type = "PR";
        final List specifics = new ArrayList<>();
        final Comment comment = new Comment(identifier, content, type, specifics);
        found.add(comment);
      }
    } catch (final Throwable e) {
      this.violationsLogger.log(SEVERE, "Could not get comments", e);
    }
    return found;
  }

  @Override
  public List getFiles() {
    final List changedFiles = new ArrayList<>();
    for (final Diff change : this.mergeRequestChanges.getChanges()) {
      final String filename = change.getNewPath();
      final List specifics = new ArrayList<>();
      final String patchString = change.getDiff();
      specifics.add(patchString);
      specifics.add(change.getOldPath());
      specifics.add(change.getNewPath());
      specifics.add(change.getNewFile().toString());
      specifics.add(change.getRenamedFile().toString());
      specifics.add(change.getDeletedFile().toString());
      final ChangedFile changedFile = new ChangedFile(filename, specifics);
      changedFiles.add(changedFile);
    }

    return changedFiles;
  }

  @Override
  public void removeComments(final List comments) {
    for (final Comment comment : comments) {
      try {
        final Long noteId = Long.parseLong(comment.getIdentifier());
        this.gitLabApi
            .getNotesApi()
            .deleteMergeRequestNote(
                this.project.getId(), this.mergeRequestChanges.getIid(), noteId);
      } catch (final Throwable e) {
        this.violationsLogger.log(SEVERE, "Could not delete note " + comment, e);
      }
    }
  }

  @Override
  public boolean shouldComment(final ChangedFile changedFile, final Integer line) {
    if (!this.api.getCommentOnlyChangedContent()) {
      return true;
    }
    final String patchString = changedFile.getSpecifics().get(0);
    if (patchString.isEmpty() && Boolean.parseBoolean(changedFile.getSpecifics().get(3))) {
      return true;
    }
    final int contextLines = this.api.getCommentOnlyChangedContentContext();
    final PatchParserUtil patch = new PatchParserUtil(patchString);
    return IntStream.rangeClosed(-contextLines, contextLines)
        .filter(i -> patch.isLineInDiff(line + i) && !patch.findOldLine(line + i).isPresent())
        .findAny()
        .isPresent();
  }

  @Override
  public boolean shouldCreateCommentWithAllSingleFileComments() {
    return this.api.getCreateCommentWithAllSingleFileComments();
  }

  @Override
  public boolean shouldCreateSingleFileComment() {
    return this.api.getCreateSingleFileComments();
  }

  @Override
  public boolean shouldKeepOldComments() {
    return this.api.getShouldKeepOldComments();
  }

  @Override
  public Optional findCommentTemplate() {
    return this.api.findCommentTemplate();
  }

  @Override
  public Integer getMaxNumberOfViolations() {
    return this.api.getMaxNumberOfViolations();
  }

  @Override
  public Integer getMaxCommentSize() {
    return this.api.getMaxCommentSize();
  }

  @Override
  public boolean shouldCommentOnlyChangedFiles() {
    return this.api.getShouldCommentOnlyChangedFiles();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy