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 : }