Line data Source code
1 : // Copyright (C) 2018 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.notedb; 16 : 17 : import static com.google.common.base.Preconditions.checkArgument; 18 : import static com.google.common.base.Preconditions.checkState; 19 : import static com.google.gerrit.server.notedb.ChangeNoteUtil.parseCommitMessageRange; 20 : import static java.util.Objects.requireNonNull; 21 : import static org.eclipse.jgit.util.RawParseUtils.decode; 22 : 23 : import com.google.gerrit.entities.Change; 24 : import com.google.gerrit.entities.RefNames; 25 : import java.io.IOException; 26 : import java.nio.charset.Charset; 27 : import java.util.Optional; 28 : import org.eclipse.jgit.lib.CommitBuilder; 29 : import org.eclipse.jgit.lib.ObjectId; 30 : import org.eclipse.jgit.lib.ObjectInserter; 31 : import org.eclipse.jgit.revwalk.RevCommit; 32 : import org.eclipse.jgit.revwalk.RevSort; 33 : import org.eclipse.jgit.revwalk.RevWalk; 34 : import org.eclipse.jgit.util.RawParseUtils; 35 : 36 : /** 37 : * Deletes a change message from NoteDb by rewriting the commit history. After deletion, the whole 38 : * change message will be replaced by a new message indicating the original change message has been 39 : * deleted for the given reason. 40 : */ 41 : public class DeleteChangeMessageRewriter implements NoteDbRewriter { 42 : 43 : private final Change.Id changeId; 44 : private final String targetMessageId; 45 : private final String newChangeMessage; 46 : 47 1 : DeleteChangeMessageRewriter(Change.Id changeId, String targetMessageId, String newChangeMessage) { 48 1 : this.changeId = changeId; 49 1 : this.targetMessageId = requireNonNull(targetMessageId); 50 1 : this.newChangeMessage = newChangeMessage; 51 1 : } 52 : 53 : @Override 54 : public String getRefName() { 55 1 : return RefNames.changeMetaRef(changeId); 56 : } 57 : 58 : @Override 59 : public ObjectId rewriteCommitHistory(RevWalk revWalk, ObjectInserter inserter, ObjectId currTip) 60 : throws IOException { 61 1 : checkArgument(!currTip.equals(ObjectId.zeroId())); 62 : 63 : // Walk from the first commit of the branch. 64 1 : revWalk.reset(); 65 1 : revWalk.markStart(revWalk.parseCommit(currTip)); 66 1 : revWalk.sort(RevSort.TOPO); 67 1 : revWalk.sort(RevSort.REVERSE); 68 : 69 1 : ObjectId newTipId = null; 70 : RevCommit originalCommit; 71 1 : boolean startRewrite = false; 72 1 : while ((originalCommit = revWalk.next()) != null) { 73 1 : boolean isTargetCommit = originalCommit.getId().getName().equals(targetMessageId); 74 1 : if (!startRewrite && !isTargetCommit) { 75 1 : newTipId = originalCommit; 76 1 : continue; 77 : } 78 : 79 1 : startRewrite = true; 80 : String newCommitMessage = 81 1 : isTargetCommit ? createNewCommitMessage(originalCommit) : originalCommit.getFullMessage(); 82 1 : newTipId = rewriteOneCommit(originalCommit, newTipId, newCommitMessage, inserter); 83 1 : } 84 1 : return newTipId; 85 : } 86 : 87 : private String createNewCommitMessage(RevCommit commit) { 88 1 : byte[] raw = commit.getRawBuffer(); 89 : 90 1 : Optional<ChangeNoteUtil.CommitMessageRange> range = parseCommitMessageRange(commit); 91 1 : checkState( 92 1 : range.isPresent() && range.get().hasChangeMessage(), "failed to parse commit message"); 93 : 94 : // Only replace the commit message body, which is the user-provided message. The subject and 95 : // footers are NoteDb metadata. 96 1 : Charset encoding = RawParseUtils.parseEncoding(raw); 97 1 : String prefix = 98 1 : decode(encoding, raw, range.get().subjectStart(), range.get().changeMessageStart()); 99 1 : String postfix = decode(encoding, raw, range.get().changeMessageEnd() + 1, raw.length); 100 1 : return prefix + newChangeMessage + postfix; 101 : } 102 : 103 : /** 104 : * Rewrites one commit. 105 : * 106 : * @param originalCommit the original commit to be rewritten. 107 : * @param parentCommitId the parent of the new commit. For the first rewritten commit, it's the 108 : * parent of 'originalCommit'. For the latter rewritten commits, it's the commit rewritten 109 : * just before it. 110 : * @param commitMessage the full commit message of the new commit. 111 : * @param inserter the {@code ObjectInserter} for the rewrite process. 112 : * @return the {@code objectId} of the new commit. 113 : */ 114 : private ObjectId rewriteOneCommit( 115 : RevCommit originalCommit, 116 : ObjectId parentCommitId, 117 : String commitMessage, 118 : ObjectInserter inserter) 119 : throws IOException { 120 1 : CommitBuilder cb = new CommitBuilder(); 121 1 : if (parentCommitId != null) { 122 1 : cb.setParentId(parentCommitId); 123 : } 124 1 : cb.setTreeId(originalCommit.getTree()); 125 1 : cb.setMessage(commitMessage); 126 1 : cb.setCommitter(originalCommit.getCommitterIdent()); 127 1 : cb.setAuthor(originalCommit.getAuthorIdent()); 128 1 : cb.setEncoding(originalCommit.getEncoding()); 129 1 : return inserter.insert(cb); 130 : } 131 : }