LCOV - code coverage report
Current view: top level - server/git - BanCommit.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 56 67 83.6 %
Date: 2022-11-19 15:00:39 Functions: 6 6 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2012 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.git;
      16             : 
      17             : import static com.google.gerrit.entities.RefNames.REFS_REJECT_COMMITS;
      18             : import static java.nio.charset.StandardCharsets.UTF_8;
      19             : 
      20             : import com.google.gerrit.entities.Project;
      21             : import com.google.gerrit.entities.RefNames;
      22             : import com.google.gerrit.extensions.restapi.AuthException;
      23             : import com.google.gerrit.server.CurrentUser;
      24             : import com.google.gerrit.server.GerritPersonIdent;
      25             : import com.google.gerrit.server.IdentifiedUser;
      26             : import com.google.gerrit.server.permissions.PermissionBackend;
      27             : import com.google.gerrit.server.permissions.PermissionBackendException;
      28             : import com.google.gerrit.server.permissions.ProjectPermission;
      29             : import com.google.inject.Inject;
      30             : import com.google.inject.Provider;
      31             : import com.google.inject.Singleton;
      32             : import java.io.IOException;
      33             : import java.time.Instant;
      34             : import java.time.ZoneId;
      35             : import java.util.List;
      36             : import org.eclipse.jgit.errors.IncorrectObjectTypeException;
      37             : import org.eclipse.jgit.errors.MissingObjectException;
      38             : import org.eclipse.jgit.lib.Constants;
      39             : import org.eclipse.jgit.lib.ObjectId;
      40             : import org.eclipse.jgit.lib.ObjectInserter;
      41             : import org.eclipse.jgit.lib.PersonIdent;
      42             : import org.eclipse.jgit.lib.Ref;
      43             : import org.eclipse.jgit.lib.Repository;
      44             : import org.eclipse.jgit.notes.Note;
      45             : import org.eclipse.jgit.notes.NoteMap;
      46             : import org.eclipse.jgit.revwalk.RevCommit;
      47             : import org.eclipse.jgit.revwalk.RevWalk;
      48             : 
      49             : /**
      50             :  * Logic for banning commits from being uploaded.
      51             :  *
      52             :  * <p>Gerrit has a per-project list of commits that are forbidden to be pushed. This class reads and
      53             :  * writes the banned commits list in {@code refs/meta/reject-commits}.
      54             :  */
      55             : @Singleton
      56             : public class BanCommit {
      57             :   /**
      58             :    * Loads a list of commits to reject from {@code refs/meta/reject-commits}.
      59             :    *
      60             :    * @param repo repository from which the rejected commits should be loaded
      61             :    * @param walk open revwalk on repo.
      62             :    * @return NoteMap of commits to be rejected, null if there are none.
      63             :    * @throws IOException the map cannot be loaded.
      64             :    */
      65             :   public static NoteMap loadRejectCommitsMap(Repository repo, RevWalk walk) throws IOException {
      66             :     try {
      67          97 :       Ref ref = repo.getRefDatabase().exactRef(RefNames.REFS_REJECT_COMMITS);
      68          97 :       if (ref == null) {
      69          96 :         return NoteMap.newEmptyMap();
      70             :       }
      71             : 
      72           2 :       RevCommit map = walk.parseCommit(ref.getObjectId());
      73           2 :       return NoteMap.read(walk.getObjectReader(), map);
      74           0 :     } catch (IOException badMap) {
      75           0 :       throw new IOException("Cannot load " + RefNames.REFS_REJECT_COMMITS, badMap);
      76             :     }
      77             :   }
      78             : 
      79             :   private final Provider<IdentifiedUser> currentUser;
      80             :   private final GitRepositoryManager repoManager;
      81             :   private final ZoneId zoneId;
      82             :   private final PermissionBackend permissionBackend;
      83             :   private final NotesBranchUtil.Factory notesBranchUtilFactory;
      84             : 
      85             :   @Inject
      86             :   BanCommit(
      87             :       Provider<IdentifiedUser> currentUser,
      88             :       GitRepositoryManager repoManager,
      89             :       @GerritPersonIdent PersonIdent gerritIdent,
      90             :       NotesBranchUtil.Factory notesBranchUtilFactory,
      91         144 :       PermissionBackend permissionBackend) {
      92         144 :     this.currentUser = currentUser;
      93         144 :     this.repoManager = repoManager;
      94         144 :     this.notesBranchUtilFactory = notesBranchUtilFactory;
      95         144 :     this.permissionBackend = permissionBackend;
      96         144 :     this.zoneId = gerritIdent.getZoneId();
      97         144 :   }
      98             : 
      99             :   /**
     100             :    * Bans a list of commits from the given project.
     101             :    *
     102             :    * <p>The user must be specified, so it can be checked for the {@code BAN_COMMIT} permission.
     103             :    */
     104             :   public BanCommitResult ban(
     105             :       Project.NameKey project, CurrentUser user, List<ObjectId> commitsToBan, String reason)
     106             :       throws AuthException, IOException, PermissionBackendException {
     107           2 :     permissionBackend.user(user).project(project).check(ProjectPermission.BAN_COMMIT);
     108             : 
     109           2 :     final BanCommitResult result = new BanCommitResult();
     110           2 :     NoteMap banCommitNotes = NoteMap.newEmptyMap();
     111             :     // Add a note for each banned commit to notes.
     112           2 :     try (Repository repo = repoManager.openRepository(project);
     113           2 :         RevWalk revWalk = new RevWalk(repo);
     114           2 :         ObjectInserter inserter = repo.newObjectInserter()) {
     115           2 :       ObjectId noteId = null;
     116           2 :       for (ObjectId commitToBan : commitsToBan) {
     117             :         try {
     118           0 :           revWalk.parseCommit(commitToBan);
     119           2 :         } catch (MissingObjectException e) {
     120             :           // Ignore exception, non-existing commits can be banned.
     121           0 :         } catch (IncorrectObjectTypeException e) {
     122           0 :           result.notACommit(commitToBan);
     123           0 :           continue;
     124           2 :         }
     125           2 :         if (noteId == null) {
     126           2 :           noteId = createNoteContent(reason, inserter);
     127             :         }
     128           2 :         banCommitNotes.set(commitToBan, noteId);
     129           2 :       }
     130           2 :       NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(project, repo, inserter);
     131           2 :       NoteMap newlyCreated =
     132           2 :           notesBranchUtil.commitNewNotes(
     133             :               banCommitNotes,
     134             :               REFS_REJECT_COMMITS,
     135           2 :               createPersonIdent(),
     136           2 :               buildCommitMessage(commitsToBan, reason));
     137             : 
     138           2 :       for (Note n : banCommitNotes) {
     139           2 :         if (newlyCreated.contains(n)) {
     140           2 :           result.commitBanned(n);
     141             :         } else {
     142           1 :           result.commitAlreadyBanned(n);
     143             :         }
     144           2 :       }
     145           2 :       return result;
     146             :     }
     147             :   }
     148             : 
     149             :   private ObjectId createNoteContent(String reason, ObjectInserter inserter) throws IOException {
     150           2 :     String noteContent = reason != null ? reason : "";
     151           2 :     if (noteContent.length() > 0 && !noteContent.endsWith("\n")) {
     152           0 :       noteContent = noteContent + "\n";
     153             :     }
     154           2 :     return inserter.insert(Constants.OBJ_BLOB, noteContent.getBytes(UTF_8));
     155             :   }
     156             : 
     157             :   private PersonIdent createPersonIdent() {
     158           2 :     return currentUser.get().newCommitterIdent(Instant.now(), zoneId);
     159             :   }
     160             : 
     161             :   private static String buildCommitMessage(List<ObjectId> bannedCommits, String reason) {
     162           2 :     final StringBuilder commitMsg = new StringBuilder();
     163           2 :     commitMsg.append("Banning ");
     164           2 :     commitMsg.append(bannedCommits.size());
     165           2 :     commitMsg.append(" ");
     166           2 :     commitMsg.append(bannedCommits.size() == 1 ? "commit" : "commits");
     167           2 :     commitMsg.append("\n\n");
     168           2 :     if (reason != null) {
     169           0 :       commitMsg.append("Reason: ");
     170           0 :       commitMsg.append(reason);
     171           0 :       commitMsg.append("\n\n");
     172             :     }
     173           2 :     commitMsg.append("The following commits are banned:\n");
     174           2 :     final StringBuilder commitList = new StringBuilder();
     175           2 :     for (ObjectId c : bannedCommits) {
     176           2 :       if (commitList.length() > 0) {
     177           0 :         commitList.append(",\n");
     178             :       }
     179           2 :       commitList.append(c.getName());
     180           2 :     }
     181           2 :     commitMsg.append(commitList);
     182           2 :     return commitMsg.toString();
     183             :   }
     184             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750