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.git.receive;
16 :
17 : import static com.google.gerrit.git.ObjectIds.abbreviateName;
18 : import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
19 :
20 : import com.google.auto.value.AutoValue;
21 : import com.google.common.collect.ImmutableList;
22 : import com.google.common.collect.ImmutableListMultimap;
23 : import com.google.common.flogger.FluentLogger;
24 : import com.google.gerrit.common.Nullable;
25 : import com.google.gerrit.entities.BranchNameKey;
26 : import com.google.gerrit.entities.Change;
27 : import com.google.gerrit.entities.Project;
28 : import com.google.gerrit.server.IdentifiedUser;
29 : import com.google.gerrit.server.events.CommitReceivedEvent;
30 : import com.google.gerrit.server.git.validators.CommitValidationException;
31 : import com.google.gerrit.server.git.validators.CommitValidationMessage;
32 : import com.google.gerrit.server.git.validators.CommitValidators;
33 : import com.google.gerrit.server.logging.TraceContext;
34 : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
35 : import com.google.gerrit.server.permissions.PermissionBackend;
36 : import com.google.gerrit.server.project.ProjectState;
37 : import com.google.gerrit.server.ssh.SshInfo;
38 : import com.google.inject.Inject;
39 : import com.google.inject.assistedinject.Assisted;
40 : import java.io.IOException;
41 : import org.eclipse.jgit.lib.Config;
42 : import org.eclipse.jgit.lib.ObjectReader;
43 : import org.eclipse.jgit.lib.Repository;
44 : import org.eclipse.jgit.notes.NoteMap;
45 : import org.eclipse.jgit.revwalk.RevCommit;
46 : import org.eclipse.jgit.transport.ReceiveCommand;
47 :
48 : /** Validates single commits for a branch. */
49 : public class BranchCommitValidator {
50 96 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
51 :
52 : private final CommitValidators.Factory commitValidatorsFactory;
53 : private final IdentifiedUser user;
54 : private final PermissionBackend.ForProject permissions;
55 : private final Project project;
56 : private final BranchNameKey branch;
57 : private final SshInfo sshInfo;
58 :
59 : interface Factory {
60 : BranchCommitValidator create(
61 : ProjectState projectState, BranchNameKey branch, IdentifiedUser user);
62 : }
63 :
64 : /** A boolean validation status and a list of additional messages. */
65 : @AutoValue
66 96 : abstract static class Result {
67 : static Result create(boolean isValid, ImmutableList<CommitValidationMessage> messages) {
68 96 : return new AutoValue_BranchCommitValidator_Result(isValid, messages);
69 : }
70 :
71 : /** Whether the commit is valid. */
72 : abstract boolean isValid();
73 :
74 : /**
75 : * A list of messages related to the validation. Messages may be present regardless of the
76 : * {@link #isValid()} status.
77 : */
78 : abstract ImmutableList<CommitValidationMessage> messages();
79 : }
80 :
81 : @Inject
82 : BranchCommitValidator(
83 : CommitValidators.Factory commitValidatorsFactory,
84 : PermissionBackend permissionBackend,
85 : SshInfo sshInfo,
86 : @Assisted ProjectState projectState,
87 : @Assisted BranchNameKey branch,
88 96 : @Assisted IdentifiedUser user) {
89 96 : this.sshInfo = sshInfo;
90 96 : this.user = user;
91 96 : this.branch = branch;
92 96 : this.commitValidatorsFactory = commitValidatorsFactory;
93 96 : project = projectState.getProject();
94 96 : permissions = permissionBackend.user(user).project(project.getNameKey());
95 96 : }
96 :
97 : /**
98 : * Validates a single commit. If the commit does not validate, the command is rejected.
99 : *
100 : * @param repository the repository
101 : * @param objectReader the object reader to use.
102 : * @param cmd the ReceiveCommand executing the push.
103 : * @param commit the commit being validated.
104 : * @param isMerged whether this is a merge commit created by magicBranch --merge option
105 : * @param change the change for which this is a new patchset.
106 : * @return The validation {@link Result}.
107 : */
108 : Result validateCommit(
109 : Repository repository,
110 : ObjectReader objectReader,
111 : ReceiveCommand cmd,
112 : RevCommit commit,
113 : ImmutableListMultimap<String, String> pushOptions,
114 : boolean isMerged,
115 : NoteMap rejectCommits,
116 : @Nullable Change change)
117 : throws IOException {
118 89 : return validateCommit(
119 : repository, objectReader, cmd, commit, pushOptions, isMerged, rejectCommits, change, false);
120 : }
121 :
122 : /**
123 : * Validates a single commit. If the commit does not validate, the command is rejected.
124 : *
125 : * @param repository the repository
126 : * @param objectReader the object reader to use.
127 : * @param cmd the ReceiveCommand executing the push.
128 : * @param commit the commit being validated.
129 : * @param isMerged whether this is a merge commit created by magicBranch --merge option
130 : * @param change the change for which this is a new patchset.
131 : * @param skipValidation whether 'skip-validation' was requested.
132 : * @return The validation {@link Result}.
133 : */
134 : Result validateCommit(
135 : Repository repository,
136 : ObjectReader objectReader,
137 : ReceiveCommand cmd,
138 : RevCommit commit,
139 : ImmutableListMultimap<String, String> pushOptions,
140 : boolean isMerged,
141 : NoteMap rejectCommits,
142 : @Nullable Change change,
143 : boolean skipValidation)
144 : throws IOException {
145 96 : try (TraceTimer traceTimer = TraceContext.newTimer("BranchCommitValidator#validateCommit")) {
146 96 : ImmutableList.Builder<CommitValidationMessage> messages = new ImmutableList.Builder<>();
147 96 : try (CommitReceivedEvent receiveEvent =
148 : new CommitReceivedEvent(
149 : cmd,
150 : project,
151 96 : branch.branch(),
152 : pushOptions,
153 96 : new Config(repository.getConfig()),
154 : objectReader,
155 : commit,
156 : user)) {
157 : CommitValidators validators;
158 96 : if (isMerged) {
159 3 : validators =
160 3 : commitValidatorsFactory.forMergedCommits(
161 3 : permissions, branch, user.asIdentifiedUser());
162 : } else {
163 96 : validators =
164 96 : commitValidatorsFactory.forReceiveCommits(
165 : permissions,
166 : branch,
167 96 : user.asIdentifiedUser(),
168 : sshInfo,
169 : rejectCommits,
170 : receiveEvent.revWalk,
171 : change,
172 : skipValidation);
173 : }
174 :
175 95 : for (CommitValidationMessage m : validators.validate(receiveEvent)) {
176 0 : messages.add(
177 : new CommitValidationMessage(
178 0 : messageForCommit(commit, m.getMessage(), objectReader), m.getType()));
179 0 : }
180 13 : } catch (CommitValidationException e) {
181 13 : logger.atFine().log("Commit validation failed on %s", commit.name());
182 13 : for (CommitValidationMessage m : e.getMessages()) {
183 : // The non-error messages may contain background explanation for the
184 : // fatal error, so have to preserve all messages.
185 9 : messages.add(
186 : new CommitValidationMessage(
187 9 : messageForCommit(commit, m.getMessage(), objectReader), m.getType()));
188 9 : }
189 13 : cmd.setResult(
190 13 : REJECTED_OTHER_REASON, messageForCommit(commit, e.getMessage(), objectReader));
191 13 : return Result.create(false, messages.build());
192 95 : }
193 95 : return Result.create(true, messages.build());
194 13 : }
195 : }
196 :
197 : private String messageForCommit(RevCommit c, String msg, ObjectReader objectReader)
198 : throws IOException {
199 13 : return String.format("commit %s: %s", abbreviateName(c, objectReader), msg);
200 : }
201 : }
|