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

com.google.gerrit.server.patch.SubmitWithStickyApprovalDiff Maven / Gradle / Ivy

There is a newer version: 3.11.0-rc3
Show newest version
// Copyright (C) 2021 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.patch;

import static com.google.gerrit.server.project.ProjectCache.illegalState;

import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.prettify.common.SparseFileContent.Accessor;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.git.validators.CommentCumulativeSizeValidator;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.lib.Config;

/**
 * This class is used on submit to compute the diff between the latest approved patch-set, and the
 * current submitted patch-set.
 *
 * 

Latest approved patch-set is defined by the latest patch-set which has Code-Review label voted * with the maximum possible value. * *

If the latest approved patch-set is the same as the submitted patch-set, the diff will be * empty. * *

We exclude the magic files from the returned diff to make it shorter and more concise. */ public class SubmitWithStickyApprovalDiff { private final ProjectCache projectCache; private final PatchScriptFactory.Factory patchScriptFactoryFactory; private final PatchListCache patchListCache; private final int maxCumulativeSize; @Inject SubmitWithStickyApprovalDiff( ProjectCache projectCache, PatchScriptFactory.Factory patchScriptFactoryFactory, PatchListCache patchListCache, @GerritServerConfig Config serverConfig) { this.projectCache = projectCache; this.patchScriptFactoryFactory = patchScriptFactoryFactory; this.patchListCache = patchListCache; maxCumulativeSize = serverConfig.getInt( "change", "cumulativeCommentSizeLimit", CommentCumulativeSizeValidator.DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT); } public String apply(ChangeNotes notes, CurrentUser currentUser) throws AuthException, IOException, PermissionBackendException, InvalidChangeOperationException { // In some submit strategies, the current patch-set doesn't exist yet as it's being created // during the submit. Hence, we assign the current patch-set to be the last existing patch-set. PatchSet currentPatchset = notes.getPatchSets().values().stream() .max((p1, p2) -> p1.id().get() - p2.id().get()) .orElseThrow( () -> new IllegalStateException( String.format( "change %s can't load any patchset", notes.getChangeId().toString()))); PatchSet.Id latestApprovedPatchsetId = getLatestApprovedPatchsetId(notes); if (latestApprovedPatchsetId.get() == currentPatchset.id().get()) { // If the latest approved patchset is the current patchset, no need to return anything. return ""; } StringBuilder diff = new StringBuilder( String.format( "\n\n%d is the latest approved patch-set.\n", latestApprovedPatchsetId.get())); PatchList patchList = getPatchList( notes.getProjectName(), currentPatchset, notes.getPatchSets().get(latestApprovedPatchsetId)); // To make the message a bit more concise, we skip the magic files. List patchListEntryList = patchList.getPatches().stream() .filter(p -> !Patch.isMagic(p.getNewName())) .collect(Collectors.toList()); if (patchListEntryList.isEmpty()) { diff.append( "No files were changed between the latest approved patch-set and the submitted one.\n"); return diff.toString(); } diff.append("The change was submitted with unreviewed changes in the following files:\n\n"); for (PatchListEntry patchListEntry : patchListEntryList) { diff.append( getDiffForFile( notes, currentPatchset.id(), latestApprovedPatchsetId, patchListEntry, currentUser)); } if (diff.length() > maxCumulativeSize) { // The diff length is not counted as part of the limit (for technical reasons, since we'd // have to call CommentCumulativeSizeValidator), but it's best not to post an extra large // change message here. return String.format( "\n\n%d is the latest approved patch-set.\nThe change was submitted " + "with many unreviewed changes (the diff is too large to show). Please review the " + "diff.", latestApprovedPatchsetId.get()); } return diff.toString(); } private String getDiffForFile( ChangeNotes notes, PatchSet.Id currentPatchsetId, PatchSet.Id latestApprovedPatchsetId, PatchListEntry patchListEntry, CurrentUser currentUser) throws AuthException, InvalidChangeOperationException, IOException, PermissionBackendException { StringBuilder diff = new StringBuilder( String.format( "The name of the file: %s\nInsertions: %d, Deletions: %d.\n\n", patchListEntry.getNewName(), patchListEntry.getInsertions(), patchListEntry.getDeletions())); DiffPreferencesInfo diffPreferencesInfo = createDefaultDiffPreferencesInfo(); PatchScriptFactory patchScriptFactory = patchScriptFactoryFactory.create( notes, patchListEntry.getNewName(), latestApprovedPatchsetId, currentPatchsetId, diffPreferencesInfo, currentUser); PatchScript patchScript = null; try { patchScript = patchScriptFactory.call(); } catch (LargeObjectException exception) { diff.append("The file content is too large for showing the full diff. \n\n"); return diff.toString(); } if (patchScript.getChangeType() == ChangeType.RENAMED) { diff.append( String.format( "The file %s was renamed to %s\n", patchListEntry.getOldName(), patchListEntry.getNewName())); } SparseFileContent.Accessor fileA = patchScript.getA().createAccessor(); SparseFileContent.Accessor fileB = patchScript.getB().createAccessor(); boolean editsExist = false; if (patchScript.getEdits().stream().anyMatch(e -> e.getType() != Edit.Type.EMPTY)) { diff.append("```\n"); editsExist = true; } for (Edit edit : patchScript.getEdits()) { diff.append(getDiffForEdit(fileA, fileB, edit)); } if (editsExist) { diff.append("```\n"); } return diff.toString(); } private String getDiffForEdit(Accessor fileA, Accessor fileB, Edit edit) { StringBuilder diff = new StringBuilder(); Edit.Type type = edit.getType(); switch (type) { case INSERT: diff.append(String.format("@@ +%d:%d @@\n", edit.getBeginB(), edit.getEndB())); diff.append(getModifiedLines(fileB, edit.getBeginB(), edit.getEndB(), '+')); diff.append("\n"); break; case DELETE: diff.append(String.format("@@ -%d:%d @@\n", edit.getBeginA(), edit.getEndA())); diff.append(getModifiedLines(fileA, edit.getBeginA(), edit.getEndA(), '-')); diff.append("\n"); break; case REPLACE: diff.append( String.format( "@@ -%d:%d, +%d:%d @@\n", edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB())); diff.append(getModifiedLines(fileA, edit.getBeginA(), edit.getEndA(), '-')); diff.append(getModifiedLines(fileB, edit.getBeginB(), edit.getEndB(), '+')); diff.append("\n"); break; case EMPTY: // do nothing since there is no change here. } return diff.toString(); } private String getModifiedLines(Accessor file, int begin, int end, char modificationType) { StringBuilder diff = new StringBuilder(); for (int i = begin; i < end; i++) { diff.append(String.format("%c %s\n", modificationType, file.get(i))); } return diff.toString(); } private DiffPreferencesInfo createDefaultDiffPreferencesInfo() { DiffPreferencesInfo diffPreferencesInfo = new DiffPreferencesInfo(); diffPreferencesInfo.ignoreWhitespace = Whitespace.IGNORE_NONE; diffPreferencesInfo.intralineDifference = true; return diffPreferencesInfo; } private PatchSet.Id getLatestApprovedPatchsetId(ChangeNotes notes) { ProjectState projectState = projectCache.get(notes.getProjectName()).orElseThrow(illegalState(notes.getProjectName())); PatchSet.Id maxPatchSetId = PatchSet.id(notes.getChangeId(), 1); for (PatchSetApproval patchSetApproval : notes.getApprovals().values()) { if (!patchSetApproval.label().equals(LabelId.CODE_REVIEW)) { continue; } if (!projectState .getLabelTypes(notes) .byLabel(patchSetApproval.labelId()) .isMaxPositive(patchSetApproval)) { continue; } if (patchSetApproval.patchSetId().get() > maxPatchSetId.get()) { maxPatchSetId = patchSetApproval.patchSetId(); } } return maxPatchSetId; } /** * Gets the {@link PatchList} between the two latest patch-sets. Can be used to compute difference * in files between those two patch-sets . */ private PatchList getPatchList(Project.NameKey project, PatchSet ps, PatchSet priorPatchSet) { PatchListKey key = PatchListKey.againstCommit( priorPatchSet.commitId(), ps.commitId(), DiffPreferencesInfo.Whitespace.IGNORE_NONE); try { return patchListCache.get(key, project); } catch (PatchListNotAvailableException ex) { throw new StorageException( "failed to compute difference in files, so won't post diff messsage on submit although " + "the latest approved patch-set was not the same as the submitted patch-set.", ex); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy