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

com.google.gerrit.server.git.validators.CommitValidators Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc4
Show newest version
// Copyright (C) 2012 The Android Open Source Project
//
// 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.gerrit.server.git.validators;

import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
import static com.google.gerrit.server.git.ReceiveCommits.NEW_PATCHSET;

import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.jcraft.jsch.HostKey;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CommitValidators {
  private static final Logger log = LoggerFactory.getLogger(CommitValidators.class);

  public enum Policy {
    /** Use {@link Factory#forGerritCommits}. */
    GERRIT,

    /** Use {@link Factory#forReceiveCommits}. */
    RECEIVE_COMMITS,

    /** Use {@link Factory#forMergedCommits}. */
    MERGED,

    /** Do not validate commits. */
    NONE
  }

  @Singleton
  public static class Factory {
    private final PersonIdent gerritIdent;
    private final String canonicalWebUrl;
    private final DynamicSet pluginValidators;
    private final AllUsersName allUsers;
    private final String installCommitMsgHookCommand;

    @Inject
    Factory(
        @GerritPersonIdent PersonIdent gerritIdent,
        @CanonicalWebUrl @Nullable String canonicalWebUrl,
        @GerritServerConfig Config cfg,
        DynamicSet pluginValidators,
        AllUsersName allUsers) {
      this.gerritIdent = gerritIdent;
      this.canonicalWebUrl = canonicalWebUrl;
      this.pluginValidators = pluginValidators;
      this.allUsers = allUsers;
      this.installCommitMsgHookCommand =
          cfg != null ? cfg.getString("gerrit", null, "installCommitMsgHookCommand") : null;
    }

    public CommitValidators create(
        Policy policy, RefControl refControl, SshInfo sshInfo, Repository repo) throws IOException {
      switch (policy) {
        case RECEIVE_COMMITS:
          return forReceiveCommits(refControl, sshInfo, repo);
        case GERRIT:
          return forGerritCommits(refControl, sshInfo, repo);
        case MERGED:
          return forMergedCommits(refControl);
        case NONE:
          return none();
        default:
          throw new IllegalArgumentException("unspported policy: " + policy);
      }
    }

    private CommitValidators forReceiveCommits(
        RefControl refControl, SshInfo sshInfo, Repository repo) throws IOException {
      try (RevWalk rw = new RevWalk(repo)) {
        NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, rw);
        return new CommitValidators(
            ImmutableList.of(
                new UploadMergesPermissionValidator(refControl),
                new AmendedGerritMergeCommitValidationListener(refControl, gerritIdent),
                new AuthorUploaderValidator(refControl, canonicalWebUrl),
                new CommitterUploaderValidator(refControl, canonicalWebUrl),
                new SignedOffByValidator(refControl),
                new ChangeIdValidator(
                    refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
                new ConfigValidator(refControl, repo, allUsers),
                new BannedCommitsValidator(rejectCommits),
                new PluginCommitValidationListener(pluginValidators),
                new BlockExternalIdUpdateListener(allUsers)));
      }
    }

    private CommitValidators forGerritCommits(
        RefControl refControl, SshInfo sshInfo, Repository repo) {
      return new CommitValidators(
          ImmutableList.of(
              new UploadMergesPermissionValidator(refControl),
              new AmendedGerritMergeCommitValidationListener(refControl, gerritIdent),
              new AuthorUploaderValidator(refControl, canonicalWebUrl),
              new SignedOffByValidator(refControl),
              new ChangeIdValidator(
                  refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
              new ConfigValidator(refControl, repo, allUsers),
              new PluginCommitValidationListener(pluginValidators),
              new BlockExternalIdUpdateListener(allUsers)));
    }

    private CommitValidators forMergedCommits(RefControl refControl) {
      // Generally only include validators that are based on permissions of the
      // user creating a change for a merged commit; generally exclude
      // validators that would require amending the change in order to correct.
      //
      // Examples:
      //  - Change-Id and Signed-off-by can't be added to an already-merged
      //    commit.
      //  - If the commit is banned, we can't ban it here. In fact, creating a
      //    review of a previously merged and recently-banned commit is a use
      //    case for post-commit code review: so reviewers have a place to
      //    discuss what to do about it.
      //  - Plugin validators may do things like require certain commit message
      //    formats, so we play it safe and exclude them.
      return new CommitValidators(
          ImmutableList.of(
              new UploadMergesPermissionValidator(refControl),
              new AuthorUploaderValidator(refControl, canonicalWebUrl),
              new CommitterUploaderValidator(refControl, canonicalWebUrl)));
    }

    private CommitValidators none() {
      return new CommitValidators(ImmutableList.of());
    }
  }

  private final List validators;

  CommitValidators(List validators) {
    this.validators = validators;
  }

  public List validate(CommitReceivedEvent receiveEvent)
      throws CommitValidationException {
    List messages = new ArrayList<>();
    try {
      for (CommitValidationListener commitValidator : validators) {
        messages.addAll(commitValidator.onCommitReceived(receiveEvent));
      }
    } catch (CommitValidationException e) {
      log.debug("CommitValidationException occurred: {}", e.getFullMessage(), e);
      // Keep the old messages (and their order) in case of an exception
      messages.addAll(e.getMessages());
      throw new CommitValidationException(e.getMessage(), messages);
    }
    return messages;
  }

  public static class ChangeIdValidator implements CommitValidationListener {
    private static final String CHANGE_ID_PREFIX = FooterConstants.CHANGE_ID.getName() + ":";
    private static final String MISSING_CHANGE_ID_MSG =
        "[%s] missing " + FooterConstants.CHANGE_ID.getName() + " in commit message footer";
    private static final String MISSING_SUBJECT_MSG =
        "[%s] missing subject; "
            + FooterConstants.CHANGE_ID.getName()
            + " must be in commit message footer";
    private static final String MULTIPLE_CHANGE_ID_MSG =
        "[%s] multiple " + FooterConstants.CHANGE_ID.getName() + " lines in commit message footer";
    private static final String INVALID_CHANGE_ID_MSG =
        "[%s] invalid "
            + FooterConstants.CHANGE_ID.getName()
            + " line format in commit message footer";
    private static final Pattern CHANGE_ID = Pattern.compile(CHANGE_ID_PATTERN);

    private final ProjectControl projectControl;
    private final String canonicalWebUrl;
    private final String installCommitMsgHookCommand;
    private final SshInfo sshInfo;
    private final IdentifiedUser user;

    public ChangeIdValidator(
        RefControl refControl,
        String canonicalWebUrl,
        String installCommitMsgHookCommand,
        SshInfo sshInfo) {
      this.projectControl = refControl.getProjectControl();
      this.canonicalWebUrl = canonicalWebUrl;
      this.installCommitMsgHookCommand = installCommitMsgHookCommand;
      this.sshInfo = sshInfo;
      this.user = projectControl.getUser().asIdentifiedUser();
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      if (!shouldValidateChangeId(receiveEvent)) {
        return Collections.emptyList();
      }
      RevCommit commit = receiveEvent.commit;
      List messages = new ArrayList<>();
      List idList = commit.getFooterLines(FooterConstants.CHANGE_ID);
      String sha1 = commit.abbreviate(RevId.ABBREV_LEN).name();

      if (idList.isEmpty()) {
        String shortMsg = commit.getShortMessage();
        if (shortMsg.startsWith(CHANGE_ID_PREFIX)
            && CHANGE_ID.matcher(shortMsg.substring(CHANGE_ID_PREFIX.length()).trim()).matches()) {
          String errMsg = String.format(MISSING_SUBJECT_MSG, sha1);
          throw new CommitValidationException(errMsg);
        }
        if (projectControl.getProjectState().isRequireChangeID()) {
          String errMsg = String.format(MISSING_CHANGE_ID_MSG, sha1);
          messages.add(getMissingChangeIdErrorMsg(errMsg, commit));
          throw new CommitValidationException(errMsg, messages);
        }
      } else if (idList.size() > 1) {
        String errMsg = String.format(MULTIPLE_CHANGE_ID_MSG, sha1);
        throw new CommitValidationException(errMsg, messages);
      } else {
        String v = idList.get(idList.size() - 1).trim();
        // Reject Change-Ids with wrong format and invalid placeholder ID from
        // Egit (I0000000000000000000000000000000000000000).
        if (!CHANGE_ID.matcher(v).matches() || v.matches("^I00*$")) {
          String errMsg = String.format(INVALID_CHANGE_ID_MSG, sha1);
          messages.add(getMissingChangeIdErrorMsg(errMsg, receiveEvent.commit));
          throw new CommitValidationException(errMsg, messages);
        }
      }
      return Collections.emptyList();
    }

    private static boolean shouldValidateChangeId(CommitReceivedEvent event) {
      return MagicBranch.isMagicBranch(event.command.getRefName())
          || NEW_PATCHSET.matcher(event.command.getRefName()).matches();
    }

    private CommitValidationMessage getMissingChangeIdErrorMsg(
        final String errMsg, final RevCommit c) {
      StringBuilder sb = new StringBuilder();
      sb.append("ERROR: ").append(errMsg);

      if (c.getFullMessage().indexOf(CHANGE_ID_PREFIX) >= 0) {
        String[] lines = c.getFullMessage().trim().split("\n");
        String lastLine = lines.length > 0 ? lines[lines.length - 1] : "";

        if (lastLine.indexOf(CHANGE_ID_PREFIX) == -1) {
          sb.append('\n');
          sb.append('\n');
          sb.append("Hint: A potential ");
          sb.append(FooterConstants.CHANGE_ID.getName());
          sb.append(" was found, but it was not in the ");
          sb.append("footer (last paragraph) of the commit message.");
        }
      }
      sb.append('\n');
      sb.append('\n');
      sb.append("Hint: To automatically insert ");
      sb.append(FooterConstants.CHANGE_ID.getName());
      sb.append(", install the hook:\n");
      sb.append(getCommitMessageHookInstallationHint());
      sb.append('\n');
      sb.append("And then amend the commit:\n");
      sb.append("  git commit --amend\n");

      return new CommitValidationMessage(sb.toString(), false);
    }

    private String getCommitMessageHookInstallationHint() {
      if (installCommitMsgHookCommand != null) {
        return installCommitMsgHookCommand;
      }
      final List hostKeys = sshInfo.getHostKeys();

      // If there are no SSH keys, the commit-msg hook must be installed via
      // HTTP(S)
      if (hostKeys.isEmpty()) {
        String p = "${gitdir}/hooks/commit-msg";
        return String.format(
            "  gitdir=$(git rev-parse --git-dir); curl -o %s %s/tools/hooks/commit-msg ; chmod +x %s",
            p, getGerritUrl(canonicalWebUrl), p);
      }

      // SSH keys exist, so the hook can be installed with scp.
      String sshHost;
      int sshPort;
      String host = hostKeys.get(0).getHost();
      int c = host.lastIndexOf(':');
      if (0 <= c) {
        if (host.startsWith("*:")) {
          sshHost = getGerritHost(canonicalWebUrl);
        } else {
          sshHost = host.substring(0, c);
        }
        sshPort = Integer.parseInt(host.substring(c + 1));
      } else {
        sshHost = host;
        sshPort = 22;
      }

      return String.format(
          "  gitdir=$(git rev-parse --git-dir); scp -p -P %d %s@%s:hooks/commit-msg ${gitdir}/hooks/",
          sshPort, user.getUserName(), sshHost);
    }
  }

  /** If this is the special project configuration branch, validate the config. */
  public static class ConfigValidator implements CommitValidationListener {
    private final RefControl refControl;
    private final Repository repo;
    private final AllUsersName allUsers;

    public ConfigValidator(RefControl refControl, Repository repo, AllUsersName allUsers) {
      this.refControl = refControl;
      this.repo = repo;
      this.allUsers = allUsers;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();

      if (REFS_CONFIG.equals(refControl.getRefName())) {
        List messages = new ArrayList<>();

        try {
          ProjectConfig cfg = new ProjectConfig(receiveEvent.project.getNameKey());
          cfg.load(repo, receiveEvent.command.getNewId());
          if (!cfg.getValidationErrors().isEmpty()) {
            addError("Invalid project configuration:", messages);
            for (ValidationError err : cfg.getValidationErrors()) {
              addError("  " + err.getMessage(), messages);
            }
            throw new ConfigInvalidException("invalid project configuration");
          }
        } catch (ConfigInvalidException | IOException e) {
          log.error(
              "User "
                  + currentUser.getUserName()
                  + " tried to push an invalid project configuration "
                  + receiveEvent.command.getNewId().name()
                  + " for project "
                  + receiveEvent.project.getName(),
              e);
          throw new CommitValidationException("invalid project configuration", messages);
        }
      }

      if (allUsers.equals(refControl.getProjectControl().getProject().getNameKey())
          && RefNames.isRefsUsers(refControl.getRefName())) {
        List messages = new ArrayList<>();
        Account.Id accountId = Account.Id.fromRef(refControl.getRefName());
        if (accountId != null) {
          try {
            WatchConfig wc = new WatchConfig(accountId);
            wc.load(repo, receiveEvent.command.getNewId());
            if (!wc.getValidationErrors().isEmpty()) {
              addError("Invalid project configuration:", messages);
              for (ValidationError err : wc.getValidationErrors()) {
                addError("  " + err.getMessage(), messages);
              }
              throw new ConfigInvalidException("invalid watch configuration");
            }
          } catch (IOException | ConfigInvalidException e) {
            log.error(
                "User "
                    + currentUser.getUserName()
                    + " tried to push an invalid watch configuration "
                    + receiveEvent.command.getNewId().name()
                    + " for account "
                    + accountId.get(),
                e);
            throw new CommitValidationException("invalid watch configuration", messages);
          }
        }
      }

      return Collections.emptyList();
    }
  }

  /** Require permission to upload merges. */
  public static class UploadMergesPermissionValidator implements CommitValidationListener {
    private final RefControl refControl;

    public UploadMergesPermissionValidator(RefControl refControl) {
      this.refControl = refControl;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      if (receiveEvent.commit.getParentCount() > 1 && !refControl.canUploadMerges()) {
        throw new CommitValidationException("you are not allowed to upload merges");
      }
      return Collections.emptyList();
    }
  }

  /** Execute commit validation plug-ins */
  public static class PluginCommitValidationListener implements CommitValidationListener {
    private final DynamicSet commitValidationListeners;

    public PluginCommitValidationListener(
        final DynamicSet commitValidationListeners) {
      this.commitValidationListeners = commitValidationListeners;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      List messages = new ArrayList<>();

      for (CommitValidationListener validator : commitValidationListeners) {
        try {
          messages.addAll(validator.onCommitReceived(receiveEvent));
        } catch (CommitValidationException e) {
          messages.addAll(e.getMessages());
          throw new CommitValidationException(e.getMessage(), messages);
        }
      }
      return messages;
    }
  }

  public static class SignedOffByValidator implements CommitValidationListener {
    private final RefControl refControl;

    public SignedOffByValidator(RefControl refControl) {
      this.refControl = refControl;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
      final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
      final PersonIdent author = receiveEvent.commit.getAuthorIdent();
      final ProjectControl projectControl = refControl.getProjectControl();

      if (projectControl.getProjectState().isUseSignedOffBy()) {
        boolean sboAuthor = false;
        boolean sboCommitter = false;
        boolean sboMe = false;
        for (final FooterLine footer : receiveEvent.commit.getFooterLines()) {
          if (footer.matches(FooterKey.SIGNED_OFF_BY)) {
            final String e = footer.getEmailAddress();
            if (e != null) {
              sboAuthor |= author.getEmailAddress().equals(e);
              sboCommitter |= committer.getEmailAddress().equals(e);
              sboMe |= currentUser.hasEmailAddress(e);
            }
          }
        }
        if (!sboAuthor && !sboCommitter && !sboMe && !refControl.canForgeCommitter()) {
          throw new CommitValidationException(
              "not Signed-off-by author/committer/uploader in commit message footer");
        }
      }
      return Collections.emptyList();
    }
  }

  /** Require that author matches the uploader. */
  public static class AuthorUploaderValidator implements CommitValidationListener {
    private final RefControl refControl;
    private final String canonicalWebUrl;

    public AuthorUploaderValidator(RefControl refControl, String canonicalWebUrl) {
      this.refControl = refControl;
      this.canonicalWebUrl = canonicalWebUrl;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
      final PersonIdent author = receiveEvent.commit.getAuthorIdent();

      if (!currentUser.hasEmailAddress(author.getEmailAddress()) && !refControl.canForgeAuthor()) {
        List messages = new ArrayList<>();

        messages.add(
            getInvalidEmailError(
                receiveEvent.commit, "author", author, currentUser, canonicalWebUrl));
        throw new CommitValidationException("invalid author", messages);
      }
      return Collections.emptyList();
    }
  }

  /** Require that committer matches the uploader. */
  public static class CommitterUploaderValidator implements CommitValidationListener {
    private final RefControl refControl;
    private final String canonicalWebUrl;

    public CommitterUploaderValidator(RefControl refControl, String canonicalWebUrl) {
      this.refControl = refControl;
      this.canonicalWebUrl = canonicalWebUrl;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
      final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
      if (!currentUser.hasEmailAddress(committer.getEmailAddress())
          && !refControl.canForgeCommitter()) {
        List messages = new ArrayList<>();
        messages.add(
            getInvalidEmailError(
                receiveEvent.commit, "committer", committer, currentUser, canonicalWebUrl));
        throw new CommitValidationException("invalid committer", messages);
      }
      return Collections.emptyList();
    }
  }

  /**
   * Don't allow the user to amend a merge created by Gerrit Code Review. This seems to happen all
   * too often, due to users not paying any attention to what they are doing.
   */
  public static class AmendedGerritMergeCommitValidationListener
      implements CommitValidationListener {
    private final PersonIdent gerritIdent;
    private final RefControl refControl;

    public AmendedGerritMergeCommitValidationListener(
        final RefControl refControl, final PersonIdent gerritIdent) {
      this.refControl = refControl;
      this.gerritIdent = gerritIdent;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      final PersonIdent author = receiveEvent.commit.getAuthorIdent();

      if (receiveEvent.commit.getParentCount() > 1
          && author.getName().equals(gerritIdent.getName())
          && author.getEmailAddress().equals(gerritIdent.getEmailAddress())
          && !refControl.canForgeGerritServerIdentity()) {
        throw new CommitValidationException("do not amend merges not made by you");
      }
      return Collections.emptyList();
    }
  }

  /** Reject banned commits. */
  public static class BannedCommitsValidator implements CommitValidationListener {
    private final NoteMap rejectCommits;

    public BannedCommitsValidator(NoteMap rejectCommits) {
      this.rejectCommits = rejectCommits;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      try {
        if (rejectCommits.contains(receiveEvent.commit)) {
          throw new CommitValidationException(
              "contains banned commit " + receiveEvent.commit.getName());
        }
        return Collections.emptyList();
      } catch (IOException e) {
        String m = "error checking banned commits";
        log.warn(m, e);
        throw new CommitValidationException(m, e);
      }
    }
  }

  /** Blocks any update to refs/meta/external-ids */
  public static class BlockExternalIdUpdateListener implements CommitValidationListener {
    private final AllUsersName allUsers;

    public BlockExternalIdUpdateListener(AllUsersName allUsers) {
      this.allUsers = allUsers;
    }

    @Override
    public List onCommitReceived(CommitReceivedEvent receiveEvent)
        throws CommitValidationException {
      if (allUsers.equals(receiveEvent.project.getNameKey())
          && RefNames.REFS_EXTERNAL_IDS.equals(receiveEvent.refName)) {
        throw new CommitValidationException("not allowed to update " + RefNames.REFS_EXTERNAL_IDS);
      }
      return Collections.emptyList();
    }
  }

  private static CommitValidationMessage getInvalidEmailError(
      RevCommit c,
      String type,
      PersonIdent who,
      IdentifiedUser currentUser,
      String canonicalWebUrl) {
    StringBuilder sb = new StringBuilder();
    sb.append("\n");
    sb.append("ERROR:  In commit ").append(c.name()).append("\n");
    sb.append("ERROR:  ")
        .append(type)
        .append(" email address ")
        .append(who.getEmailAddress())
        .append("\n");
    sb.append("ERROR:  does not match your user account.\n");
    sb.append("ERROR:\n");
    if (currentUser.getEmailAddresses().isEmpty()) {
      sb.append("ERROR:  You have not registered any email addresses.\n");
    } else {
      sb.append("ERROR:  The following addresses are currently registered:\n");
      for (String address : currentUser.getEmailAddresses()) {
        sb.append("ERROR:    ").append(address).append("\n");
      }
    }
    sb.append("ERROR:\n");
    if (canonicalWebUrl != null) {
      sb.append("ERROR:  To register an email address, please visit:\n");
      sb.append("ERROR:  ")
          .append(canonicalWebUrl)
          .append("#")
          .append(PageLinks.SETTINGS_CONTACT)
          .append("\n");
    }
    sb.append("\n");
    return new CommitValidationMessage(sb.toString(), false);
  }

  /**
   * Get the Gerrit URL.
   *
   * @return the canonical URL (with any trailing slash removed) if it is configured, otherwise fall
   *     back to "http://hostname" where hostname is the value returned by {@link
   *     #getGerritHost(String)}.
   */
  private static String getGerritUrl(String canonicalWebUrl) {
    if (canonicalWebUrl != null) {
      return CharMatcher.is('/').trimTrailingFrom(canonicalWebUrl);
    }
    return "http://" + getGerritHost(canonicalWebUrl);
  }

  /**
   * Get the Gerrit hostname.
   *
   * @return the hostname from the canonical URL if it is configured, otherwise whatever the OS says
   *     the hostname is.
   */
  private static String getGerritHost(String canonicalWebUrl) {
    String host;
    if (canonicalWebUrl != null) {
      try {
        host = new URL(canonicalWebUrl).getHost();
      } catch (MalformedURLException e) {
        host = SystemReader.getInstance().getHostname();
      }
    } else {
      host = SystemReader.getInstance().getHostname();
    }
    return host;
  }

  private static void addError(String error, List messages) {
    messages.add(new CommitValidationMessage(error, true));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy