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

com.google.gerrit.server.restapi.change.PatchSetCreator Maven / Gradle / Ivy

There is a newer version: 3.11.1
Show newest version
// Copyright (C) 2024 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.restapi.change;

import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.CHANGE_MODIFICATION;
import static java.util.Objects.requireNonNull;

import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.PreconditionFailedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.CommitUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.gerrit.server.util.time.TimeUtil;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;

/** A utility class for creating a patch set on an existing change. */
@Singleton
public class PatchSetCreator {
  private final Provider ident;
  private final BatchUpdate.Factory batchUpdateFactory;
  private final PatchSetInserter.Factory patchSetInserterFactory;
  private final ChangeJson.Factory jsonFactory;
  private final ZoneId serverZoneId;

  @Inject
  PatchSetCreator(
      Provider ident,
      BatchUpdate.Factory batchUpdateFactory,
      PatchSetInserter.Factory patchSetInserterFactory,
      @GerritPersonIdent PersonIdent myIdent,
      ChangeJson.Factory jsonFactory) {
    this.ident = ident;
    this.batchUpdateFactory = batchUpdateFactory;
    this.patchSetInserterFactory = patchSetInserterFactory;
    this.serverZoneId = myIdent.getZoneId();
    this.jsonFactory = jsonFactory;
  }

  public ChangeInfo createPatchSetWithSuppliedTree(
      Project.NameKey project,
      ChangeData destChange,
      RevCommit latestPatchset,
      List parents,
      @Nullable AccountInput author,
      List outputOptions,
      Repository repo,
      ObjectInserter oi,
      CodeReviewRevWalk revWalk,
      ObjectId commitTree,
      String commitMessage)
      throws IOException, RestApiException, UpdateException {
    requireNonNull(destChange);
    requireNonNull(latestPatchset);
    requireNonNull(parents);
    requireNonNull(outputOptions);

    Instant now = TimeUtil.now();
    PersonIdent committerIdent =
        Optional.ofNullable(latestPatchset.getCommitterIdent())
            .map(
                id ->
                    ident
                        .get()
                        .newCommitterIdent(id.getEmailAddress(), now, serverZoneId)
                        .orElseGet(() -> ident.get().newCommitterIdent(now, serverZoneId)))
            .orElseGet(() -> ident.get().newCommitterIdent(now, serverZoneId));
    PersonIdent authorIdent =
        author == null
            ? committerIdent
            : new PersonIdent(author.name, author.email, now, serverZoneId);

    ObjectId appliedCommit =
        CommitUtil.createCommitWithTree(
            oi, authorIdent, committerIdent, parents, commitMessage, commitTree);
    CodeReviewCommit commit = revWalk.parseCommit(appliedCommit);
    oi.flush();

    Change resultChange;
    try (BatchUpdate bu = batchUpdateFactory.create(project, ident.get(), TimeUtil.now())) {
      bu.setRepository(repo, revWalk, oi);
      resultChange = insertPatchSet(bu, repo, patchSetInserterFactory, destChange.notes(), commit);
    } catch (NoSuchChangeException | RepositoryNotFoundException e) {
      throw new ResourceConflictException(e.getMessage());
    }

    return jsonFactory.create(outputOptions).format(resultChange);
  }

  public void validateChangeCanBeAppended(@Nullable ChangeData destChange, BranchNameKey destBranch)
      throws PreconditionFailedException {
    if (destChange == null) {
      throw new PreconditionFailedException(
          "cannot write a patch set without a destination change.");
    }

    if (destChange.change().isClosed()) {
      throw new PreconditionFailedException(
          String.format(
              "patch:apply with Change-Id %s could not update the existing change %d "
                  + "in destination branch %s of project %s, because the change was closed (%s)",
              destChange.getId(),
              destChange.getId().get(),
              destBranch.branch(),
              destBranch.project(),
              destChange.change().getStatus().name()));
    }
  }

  private static Change insertPatchSet(
      BatchUpdate bu,
      Repository git,
      PatchSetInserter.Factory patchSetInserterFactory,
      ChangeNotes destNotes,
      CodeReviewCommit commit)
      throws IOException, UpdateException, RestApiException {
    try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
      Change destChange = destNotes.getChange();
      PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
      PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, commit);
      inserter.setMessage(buildMessageForPatchSet(psId));
      bu.addOp(destChange.getId(), inserter);
      bu.execute();
      return inserter.getChange();
    }
  }

  private static String buildMessageForPatchSet(PatchSet.Id psId) {
    return String.format("Uploaded patch set %s.", psId.get());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy