LCOV - code coverage report
Current view: top level - server/edit/tree - ChangeFileContentModification.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 39 53 73.6 %
Date: 2022-11-19 15:00:39 Functions: 11 12 91.7 %

          Line data    Source code
       1             : // Copyright (C) 2017 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.edit.tree;
      16             : 
      17             : import static java.util.Objects.requireNonNull;
      18             : import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
      19             : 
      20             : import com.google.common.annotations.VisibleForTesting;
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.common.collect.ImmutableSet;
      23             : import com.google.common.flogger.FluentLogger;
      24             : import com.google.common.io.ByteStreams;
      25             : import com.google.gerrit.common.Nullable;
      26             : import com.google.gerrit.extensions.restapi.RawInput;
      27             : import java.io.IOException;
      28             : import java.io.InputStream;
      29             : import java.time.Instant;
      30             : import java.util.Collections;
      31             : import java.util.List;
      32             : import org.eclipse.jgit.dircache.DirCacheEditor;
      33             : import org.eclipse.jgit.dircache.DirCacheEntry;
      34             : import org.eclipse.jgit.errors.InvalidObjectIdException;
      35             : import org.eclipse.jgit.lib.FileMode;
      36             : import org.eclipse.jgit.lib.ObjectId;
      37             : import org.eclipse.jgit.lib.ObjectInserter;
      38             : import org.eclipse.jgit.lib.Repository;
      39             : 
      40             : /** A {@code TreeModification} which changes the content of a file. */
      41             : public class ChangeFileContentModification implements TreeModification {
      42          26 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      43             : 
      44             :   private final String filePath;
      45             :   private final RawInput newContent;
      46             :   private final Integer newGitFileMode;
      47             : 
      48           4 :   public ChangeFileContentModification(String filePath, RawInput newContent) {
      49           4 :     this.filePath = filePath;
      50           4 :     this.newContent = requireNonNull(newContent, "new content required");
      51           4 :     this.newGitFileMode = null;
      52           4 :   }
      53             : 
      54             :   public ChangeFileContentModification(
      55          23 :       String filePath, RawInput newContent, @Nullable Integer newGitFileMode) {
      56          23 :     this.filePath = filePath;
      57          23 :     this.newContent = requireNonNull(newContent, "new content required");
      58          23 :     this.newGitFileMode = newGitFileMode;
      59          23 :   }
      60             : 
      61             :   @Override
      62             :   public List<DirCacheEditor.PathEdit> getPathEdits(
      63             :       Repository repository, ObjectId treeId, ImmutableList<? extends ObjectId> parents) {
      64          26 :     DirCacheEditor.PathEdit changeContentEdit =
      65             :         new ChangeContent(filePath, newContent, repository, newGitFileMode);
      66          26 :     return Collections.singletonList(changeContentEdit);
      67             :   }
      68             : 
      69             :   @Override
      70             :   public ImmutableSet<String> getFilePaths() {
      71          26 :     return ImmutableSet.of(filePath);
      72             :   }
      73             : 
      74             :   @VisibleForTesting
      75             :   RawInput getNewContent() {
      76           1 :     return newContent;
      77             :   }
      78             : 
      79             :   /** A {@code PathEdit} which changes the contents of a file. */
      80             :   private static class ChangeContent extends DirCacheEditor.PathEdit {
      81             : 
      82             :     private final RawInput newContent;
      83             :     private final Repository repository;
      84             :     private final Integer newGitFileMode;
      85             : 
      86             :     ChangeContent(
      87             :         String filePath,
      88             :         RawInput newContent,
      89             :         Repository repository,
      90             :         @Nullable Integer newGitFileMode) {
      91          26 :       super(filePath);
      92          26 :       this.newContent = newContent;
      93          26 :       this.repository = repository;
      94          26 :       this.newGitFileMode = newGitFileMode;
      95          26 :     }
      96             : 
      97             :     private boolean isValidGitFileMode(int gitFileMode) {
      98           1 :       return (gitFileMode == 100755) || (gitFileMode == 100644);
      99             :     }
     100             : 
     101             :     @Override
     102             :     public void apply(DirCacheEntry dirCacheEntry) {
     103             :       try {
     104          26 :         if (newGitFileMode != null && newGitFileMode != 0) {
     105           1 :           if (!isValidGitFileMode(newGitFileMode)) {
     106           0 :             throw new IllegalStateException("GitFileMode " + newGitFileMode + " is invalid");
     107             :           }
     108           1 :           dirCacheEntry.setFileMode(FileMode.fromBits(newGitFileMode));
     109             :         }
     110          26 :         if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
     111           0 :           dirCacheEntry.setLength(0);
     112           0 :           dirCacheEntry.setLastModified(Instant.EPOCH);
     113           0 :           ObjectId newObjectId = ObjectId.fromString(getNewContentBytes(), 0);
     114           0 :           dirCacheEntry.setObjectId(newObjectId);
     115           0 :         } else {
     116          26 :           if (dirCacheEntry.getRawMode() == 0) {
     117          19 :             dirCacheEntry.setFileMode(FileMode.REGULAR_FILE);
     118             :           }
     119          26 :           ObjectId newBlobObjectId = createNewBlobAndGetItsId();
     120          26 :           dirCacheEntry.setObjectId(newBlobObjectId);
     121             :         }
     122             :         // Previously, these two exceptions were swallowed. To improve the
     123             :         // situation, we log them now. However, we should think of a better
     124             :         // approach.
     125           0 :       } catch (IOException e) {
     126           0 :         String message =
     127           0 :             String.format("Could not change the content of %s", dirCacheEntry.getPathString());
     128           0 :         logger.atSevere().withCause(e).log("%s", message);
     129           0 :       } catch (InvalidObjectIdException e) {
     130           0 :         logger.atSevere().withCause(e).log("Invalid object id in submodule link");
     131          26 :       }
     132          26 :     }
     133             : 
     134             :     private ObjectId createNewBlobAndGetItsId() throws IOException {
     135          26 :       try (ObjectInserter objectInserter = repository.newObjectInserter()) {
     136          26 :         ObjectId blobObjectId = createNewBlobAndGetItsId(objectInserter);
     137          26 :         objectInserter.flush();
     138          26 :         return blobObjectId;
     139             :       }
     140             :     }
     141             : 
     142             :     private ObjectId createNewBlobAndGetItsId(ObjectInserter objectInserter) throws IOException {
     143          26 :       long contentLength = newContent.getContentLength();
     144          26 :       if (contentLength < 0) {
     145           0 :         return objectInserter.insert(OBJ_BLOB, getNewContentBytes());
     146             :       }
     147          26 :       InputStream contentInputStream = newContent.getInputStream();
     148          26 :       return objectInserter.insert(OBJ_BLOB, contentLength, contentInputStream);
     149             :     }
     150             : 
     151             :     private byte[] getNewContentBytes() throws IOException {
     152           0 :       return ByteStreams.toByteArray(newContent.getInputStream());
     153             :     }
     154             :   }
     155             : }

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