Line data Source code
1 : // Copyright (C) 2014 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 :
20 : import com.google.common.flogger.FluentLogger;
21 : import com.google.gerrit.common.Nullable;
22 : import com.google.gerrit.entities.Account;
23 : import com.google.gerrit.entities.Change;
24 : import com.google.gerrit.entities.Comment;
25 : import com.google.gerrit.entities.PatchSet;
26 : import com.google.gerrit.entities.Project;
27 : import com.google.gerrit.server.CurrentUser;
28 : import com.google.gerrit.server.IdentifiedUser;
29 : import com.google.gerrit.server.InternalUser;
30 : import java.io.IOException;
31 : import java.time.Instant;
32 : import org.eclipse.jgit.lib.CommitBuilder;
33 : import org.eclipse.jgit.lib.Constants;
34 : import org.eclipse.jgit.lib.ObjectId;
35 : import org.eclipse.jgit.lib.ObjectInserter;
36 : import org.eclipse.jgit.lib.PersonIdent;
37 : import org.eclipse.jgit.revwalk.RevCommit;
38 : import org.eclipse.jgit.revwalk.RevWalk;
39 :
40 : /** A single delta related to a specific patch-set of a change. */
41 : public abstract class AbstractChangeUpdate {
42 103 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
43 :
44 : protected final ChangeNoteUtil noteUtil;
45 : protected final Account.Id accountId;
46 : protected final Account.Id realAccountId;
47 : protected final PersonIdent authorIdent;
48 : protected final Instant when;
49 :
50 : @Nullable private final ChangeNotes notes;
51 : private final Change change;
52 : protected final PersonIdent serverIdent;
53 :
54 : @Nullable protected PatchSet.Id psId;
55 : private ObjectId result;
56 : boolean rootOnly;
57 :
58 : AbstractChangeUpdate(
59 : ChangeNotes notes,
60 : CurrentUser user,
61 : PersonIdent serverIdent,
62 : ChangeNoteUtil noteUtil,
63 103 : Instant when) {
64 103 : this.noteUtil = noteUtil;
65 103 : this.serverIdent = new PersonIdent(serverIdent, when);
66 103 : this.notes = notes;
67 103 : this.change = notes.getChange();
68 103 : this.accountId = accountId(user);
69 103 : Account.Id realAccountId = accountId(user.getRealUser());
70 103 : this.realAccountId = realAccountId != null ? realAccountId : accountId;
71 103 : this.authorIdent = ident(noteUtil, serverIdent, user, when);
72 103 : this.when = when;
73 103 : }
74 :
75 : AbstractChangeUpdate(
76 : ChangeNoteUtil noteUtil,
77 : PersonIdent serverIdent,
78 : @Nullable ChangeNotes notes,
79 : @Nullable Change change,
80 : Account.Id accountId,
81 : Account.Id realAccountId,
82 : PersonIdent authorIdent,
83 29 : Instant when) {
84 29 : checkArgument(
85 : (notes != null && change == null) || (notes == null && change != null),
86 : "exactly one of notes or change required");
87 29 : this.noteUtil = noteUtil;
88 29 : this.serverIdent = new PersonIdent(serverIdent, when);
89 29 : this.notes = notes;
90 29 : this.change = change != null ? change : notes.getChange();
91 29 : this.accountId = accountId;
92 29 : this.realAccountId = realAccountId;
93 29 : this.authorIdent = authorIdent;
94 29 : this.when = when;
95 29 : }
96 :
97 : private static void checkUserType(CurrentUser user) {
98 103 : checkArgument(
99 : (user instanceof IdentifiedUser) || (user instanceof InternalUser),
100 : "user must be IdentifiedUser or InternalUser: %s",
101 : user);
102 103 : }
103 :
104 : @Nullable
105 : private static Account.Id accountId(CurrentUser u) {
106 103 : checkUserType(u);
107 103 : return (u instanceof IdentifiedUser) ? u.getAccountId() : null;
108 : }
109 :
110 : private static PersonIdent ident(
111 : ChangeNoteUtil noteUtil, PersonIdent serverIdent, CurrentUser u, Instant when) {
112 103 : checkUserType(u);
113 103 : if (u instanceof IdentifiedUser) {
114 103 : return noteUtil.newAccountIdIdent(u.asIdentifiedUser().getAccount().id(), when, serverIdent);
115 2 : } else if (u instanceof InternalUser) {
116 2 : return serverIdent;
117 : }
118 0 : throw new IllegalStateException();
119 : }
120 :
121 : public Change.Id getId() {
122 103 : return change.getId();
123 : }
124 :
125 : /**
126 : * Returns notes for the state of this change prior to this update. If this update is part of a
127 : * series managed by a {@link NoteDbUpdateManager}, then this reflects the state prior to the
128 : * first update in the series. A null return value can only happen when the change is being
129 : * rebuilt from NoteDb. A change that is in the process of being created will result in a non-null
130 : * return value from this method, but a null return value from {@link ChangeNotes#getRevision()}.
131 : */
132 : @Nullable
133 : public ChangeNotes getNotes() {
134 103 : return notes;
135 : }
136 :
137 : public Change getChange() {
138 103 : return change;
139 : }
140 :
141 : public Instant getWhen() {
142 103 : return when;
143 : }
144 :
145 : public PatchSet.Id getPatchSetId() {
146 103 : return psId;
147 : }
148 :
149 : public void setPatchSetId(PatchSet.Id psId) {
150 103 : checkArgument(psId == null || psId.changeId().equals(getId()));
151 103 : this.psId = psId;
152 103 : }
153 :
154 : public Account.Id getAccountId() {
155 103 : checkState(
156 : accountId != null,
157 : "author identity for %s is not from an IdentifiedUser: %s",
158 103 : getClass().getSimpleName(),
159 103 : authorIdent.toExternalString());
160 103 : return accountId;
161 : }
162 :
163 : public Account.Id getNullableAccountId() {
164 0 : return accountId;
165 : }
166 :
167 : /** Whether no updates have been done. */
168 : public abstract boolean isEmpty();
169 :
170 : /** Wether this update can only be a root commit. */
171 : boolean isRootOnly() {
172 103 : return rootOnly;
173 : }
174 :
175 : /**
176 : * Returns the NameKey for the project where the update will be stored, which is not necessarily
177 : * the same as the change's project.
178 : */
179 : protected abstract Project.NameKey getProjectName();
180 :
181 : protected abstract String getRefName();
182 :
183 : protected void setParentCommit(CommitBuilder cb, ObjectId parentCommitId) {
184 103 : if (!parentCommitId.equals(ObjectId.zeroId())) {
185 90 : cb.setParentId(parentCommitId);
186 : } else {
187 103 : cb.setParentIds(); // Ref is currently nonexistent, commit has no parents.
188 : }
189 103 : }
190 :
191 : /**
192 : * Whether to allow bypassing the check that an update does not exceed the max update count on an
193 : * object.
194 : */
195 : protected boolean bypassMaxUpdates() {
196 0 : return false;
197 : }
198 :
199 : /**
200 : * Apply this update to the given inserter.
201 : *
202 : * @param rw walk for reading back any objects needed for the update.
203 : * @param ins inserter to write to; callers should not flush.
204 : * @param curr the current tip of the branch prior to this update.
205 : * @return commit ID produced by inserting this update's commit, or null if this update is a no-op
206 : * and should be skipped. The zero ID is a valid return value, and indicates the ref should be
207 : * deleted.
208 : * @throws IOException if a lower-level error occurred.
209 : */
210 : @Nullable
211 : final ObjectId apply(RevWalk rw, ObjectInserter ins, ObjectId curr) throws IOException {
212 103 : if (isEmpty()) {
213 22 : return null;
214 : }
215 :
216 103 : checkArgument(rw.getObjectReader().getCreatedFromInserter() == ins);
217 :
218 103 : logger.atFinest().log(
219 : "%s for change %s of project %s in %s (NoteDb)",
220 103 : getClass().getSimpleName(), getId(), getProjectName(), getRefName());
221 :
222 103 : ObjectId z = ObjectId.zeroId();
223 103 : CommitBuilder cb = applyImpl(rw, ins, curr);
224 103 : if (cb == null) {
225 17 : result = z;
226 17 : return z; // Impl intends to delete the ref.
227 103 : } else if (cb == NO_OP_UPDATE) {
228 34 : return null; // Impl is a no-op.
229 : }
230 103 : cb.setAuthor(authorIdent);
231 103 : cb.setCommitter(new PersonIdent(serverIdent, when));
232 103 : setParentCommit(cb, curr);
233 103 : if (cb.getTreeId() == null) {
234 103 : if (curr.equals(z)) {
235 103 : cb.setTreeId(emptyTree(ins)); // No parent, assume empty tree.
236 : } else {
237 84 : RevCommit p = rw.parseCommit(curr);
238 84 : cb.setTreeId(p.getTree()); // Copy tree from parent.
239 : }
240 : }
241 103 : result = ins.insert(cb);
242 103 : return result;
243 : }
244 :
245 : /**
246 : * Create a commit containing the contents of this update.
247 : *
248 : * @param ins inserter to write to; callers should not flush.
249 : * @return a new commit builder representing this commit, or null to indicate the meta ref should
250 : * be deleted as a result of this update. The parent, author, and committer fields in the
251 : * return value are always overwritten. The tree ID may be unset by this method, which
252 : * indicates to the caller that it should be copied from the parent commit. To indicate that
253 : * this update is a no-op (but this could not be determined by {@link #isEmpty()}), return the
254 : * sentinel {@link #NO_OP_UPDATE}.
255 : * @throws IOException if a lower-level error occurred.
256 : */
257 : protected abstract CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins, ObjectId curr)
258 : throws IOException;
259 :
260 103 : static final CommitBuilder NO_OP_UPDATE = new CommitBuilder();
261 :
262 : ObjectId getResult() {
263 2 : return result;
264 : }
265 :
266 : public boolean allowWriteToNewRef() {
267 29 : return true;
268 : }
269 :
270 : private static ObjectId emptyTree(ObjectInserter ins) throws IOException {
271 103 : return ins.insert(Constants.OBJ_TREE, new byte[] {});
272 : }
273 :
274 : void verifyComment(Comment c) {
275 29 : checkArgument(c.getCommitId() != null, "commit ID required for comment: %s", c);
276 29 : checkArgument(
277 29 : c.author.getId().equals(getAccountId()),
278 : "The author for the following comment does not match the author of this %s (%s): %s",
279 29 : getClass().getSimpleName(),
280 29 : getAccountId(),
281 : c);
282 29 : checkArgument(
283 29 : c.getRealAuthor().getId().equals(realAccountId),
284 : "The real author for the following comment does not match the real"
285 : + " author of this %s (%s): %s",
286 29 : getClass().getSimpleName(),
287 : realAccountId,
288 : c);
289 29 : }
290 : }
|