Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  ElasticVersionTest: Add missing version 7.1 asserts
  ElasticVersionTest: Improve test method ordering
  ElasticVersionTest: Make test method names accurate
  ReceiveCommits: Validate ref operation before commits for non-ff case
  Add test coverage for ref operation validation extension point
  Allow CommitValidationListener to ignore 'skip validation' push option
  Upgrade JGit to 4.11.8.201904181247-r
  CreateProject: Expose createProject method
  ElasticContainer: Switch to Alpine-based images from blacktop
  DeleteVote.soy: Make wording consistent with DeleteVoteHtml.soy
  DeleteVote.soy add missing review URL
  NewChange.soy: add missing closing parenthesis
  Update git submodules
  gerrit.sh: Fix message about JRE
  Allow CommitValidationListener to ignore 'skip validation' push option
  Bazel: Fix lint warning flagged by buildifier

Change-Id: I5ca13ae4495265592ac07381bc97370ec025d3f5
This commit is contained in:
Marco Miller
2019-06-26 16:32:03 -04:00
18 changed files with 366 additions and 64 deletions

View File

@@ -36,6 +36,8 @@ import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/** /**
* A set of members that can be modified as plugins reload. * A set of members that can be modified as plugins reload.
@@ -284,6 +286,10 @@ public class DynamicSet<T> implements Iterable<T> {
return new ReloadableHandle(ref, key, ref.get()); return new ReloadableHandle(ref, key, ref.get());
} }
public Stream<T> stream() {
return StreamSupport.stream(spliterator(), false);
}
private class ReloadableHandle implements ReloadableRegistrationHandle<T> { private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
private final AtomicReference<Extension<T>> ref; private final AtomicReference<Extension<T>> ref;
private final Key<T> key; private final Key<T> key;

View File

@@ -90,6 +90,29 @@ public class BranchCommitValidator {
NoteMap rejectCommits, NoteMap rejectCommits,
@Nullable Change change) @Nullable Change change)
throws IOException { throws IOException {
return validCommit(objectReader, cmd, commit, isMerged, messages, rejectCommits, change, false);
}
/**
* Validates a single commit. If the commit does not validate, the command is rejected.
*
* @param objectReader the object reader to use.
* @param cmd the ReceiveCommand executing the push.
* @param commit the commit being validated.
* @param isMerged whether this is a merge commit created by magicBranch --merge option
* @param change the change for which this is a new patchset.
* @param skipValidation whether 'skip-validation' was requested.
*/
public boolean validCommit(
ObjectReader objectReader,
ReceiveCommand cmd,
RevCommit commit,
boolean isMerged,
List<ValidationMessage> messages,
NoteMap rejectCommits,
@Nullable Change change,
boolean skipValidation)
throws IOException {
try (CommitReceivedEvent receiveEvent = try (CommitReceivedEvent receiveEvent =
new CommitReceivedEvent(cmd, project, branch.get(), objectReader, commit, user)) { new CommitReceivedEvent(cmd, project, branch.get(), objectReader, commit, user)) {
CommitValidators validators; CommitValidators validators;
@@ -105,7 +128,8 @@ public class BranchCommitValidator {
sshInfo, sshInfo,
rejectCommits, rejectCommits,
receiveEvent.revWalk, receiveEvent.revWalk,
change); change,
skipValidation);
} }
for (CommitValidationMessage m : validators.validate(receiveEvent)) { for (CommitValidationMessage m : validators.validate(receiveEvent)) {

View File

@@ -1298,11 +1298,8 @@ class ReceiveCommits {
} }
private void parseRewind(ReceiveCommand cmd) throws PermissionBackendException { private void parseRewind(ReceiveCommand cmd) throws PermissionBackendException {
RevCommit newObject;
try { try {
newObject = receivePack.getRevWalk().parseCommit(cmd.getNewId()); receivePack.getRevWalk().parseCommit(cmd.getNewId());
} catch (IncorrectObjectTypeException notCommit) {
newObject = null;
} catch (IOException err) { } catch (IOException err) {
logger.atSevere().withCause(err).log( logger.atSevere().withCause(err).log(
"Invalid object %s for %s forced update", cmd.getNewId().name(), cmd.getRefName()); "Invalid object %s for %s forced update", cmd.getNewId().name(), cmd.getRefName());
@@ -1311,12 +1308,13 @@ class ReceiveCommits {
} }
logger.atFine().log("Rewinding %s", cmd); logger.atFine().log("Rewinding %s", cmd);
if (newObject != null) { if (!validRefOperation(cmd)) {
return;
}
validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd); validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
if (cmd.getResult() != NOT_ATTEMPTED) { if (cmd.getResult() != NOT_ATTEMPTED) {
return; return;
} }
}
Optional<AuthException> err = checkRefPermission(cmd, RefPermission.FORCE_UPDATE); Optional<AuthException> err = checkRefPermission(cmd, RefPermission.FORCE_UPDATE);
if (!err.isPresent()) { if (!err.isPresent()) {
@@ -3064,10 +3062,12 @@ class ReceiveCommits {
*/ */
private void validateRegularPushCommits(Branch.NameKey branch, ReceiveCommand cmd) private void validateRegularPushCommits(Branch.NameKey branch, ReceiveCommand cmd)
throws PermissionBackendException { throws PermissionBackendException {
if (!RefNames.REFS_CONFIG.equals(cmd.getRefName()) boolean skipValidation =
!RefNames.REFS_CONFIG.equals(cmd.getRefName())
&& !(MagicBranch.isMagicBranch(cmd.getRefName()) && !(MagicBranch.isMagicBranch(cmd.getRefName())
|| NEW_PATCHSET_PATTERN.matcher(cmd.getRefName()).matches()) || NEW_PATCHSET_PATTERN.matcher(cmd.getRefName()).matches())
&& pushOptions.containsKey(PUSH_OPTION_SKIP_VALIDATION)) { && pushOptions.containsKey(PUSH_OPTION_SKIP_VALIDATION);
if (skipValidation) {
if (projectState.is(BooleanProjectConfig.USE_SIGNED_OFF_BY)) { if (projectState.is(BooleanProjectConfig.USE_SIGNED_OFF_BY)) {
reject(cmd, "requireSignedOffBy prevents option " + PUSH_OPTION_SKIP_VALIDATION); reject(cmd, "requireSignedOffBy prevents option " + PUSH_OPTION_SKIP_VALIDATION);
return; return;
@@ -3082,11 +3082,8 @@ class ReceiveCommits {
if (!Iterables.isEmpty(rejectCommits)) { if (!Iterables.isEmpty(rejectCommits)) {
reject(cmd, "reject-commits prevents " + PUSH_OPTION_SKIP_VALIDATION); reject(cmd, "reject-commits prevents " + PUSH_OPTION_SKIP_VALIDATION);
} }
logger.atFine().log("Short-circuiting new commit validation");
return;
} }
BranchCommitValidator validator = commitValidatorFactory.create(projectState, branch, user);
RevWalk walk = receivePack.getRevWalk(); RevWalk walk = receivePack.getRevWalk();
walk.reset(); walk.reset();
walk.sort(RevSort.NONE); walk.sort(RevSort.NONE);
@@ -3101,7 +3098,7 @@ class ReceiveCommits {
int limit = receiveConfig.maxBatchCommits; int limit = receiveConfig.maxBatchCommits;
int n = 0; int n = 0;
for (RevCommit c; (c = walk.next()) != null; ) { for (RevCommit c; (c = walk.next()) != null; ) {
if (++n > limit) { if (++n > limit && !skipValidation) {
logger.atFine().log("Number of new commits exceeds limit of %d", limit); logger.atFine().log("Number of new commits exceeds limit of %d", limit);
reject( reject(
cmd, cmd,
@@ -3113,8 +3110,9 @@ class ReceiveCommits {
continue; continue;
} }
BranchCommitValidator validator = commitValidatorFactory.create(projectState, branch, user);
if (!validator.validCommit( if (!validator.validCommit(
walk.getObjectReader(), cmd, c, false, messages, rejectCommits, null)) { walk.getObjectReader(), cmd, c, false, messages, rejectCommits, null, skipValidation)) {
break; break;
} }
} }

View File

@@ -35,4 +35,14 @@ public interface CommitValidationListener {
*/ */
List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException; throws CommitValidationException;
/**
* Whether this validator should validate all commits.
*
* @return {@code true} if this validator should validate all commits, even when the {@code
* skip-validation} push option was specified.
*/
default boolean shouldValidateAllCommits() {
return false;
}
} }

View File

@@ -132,7 +132,8 @@ public class CommitValidators {
SshInfo sshInfo, SshInfo sshInfo,
NoteMap rejectCommits, NoteMap rejectCommits,
RevWalk rw, RevWalk rw,
@Nullable Change change) @Nullable Change change,
boolean skipValidation)
throws IOException { throws IOException {
PermissionBackend.ForRef perm = forProject.ref(branch.get()); PermissionBackend.ForRef perm = forProject.ref(branch.get());
ProjectState projectState = projectCache.checkedGet(branch.getParentKey()); ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
@@ -153,7 +154,7 @@ public class CommitValidators {
change), change),
new ConfigValidator(projectConfigFactory, branch, user, rw, allUsers, allProjects), new ConfigValidator(projectConfigFactory, branch, user, rw, allUsers, allProjects),
new BannedCommitsValidator(rejectCommits), new BannedCommitsValidator(rejectCommits),
new PluginCommitValidationListener(pluginValidators), new PluginCommitValidationListener(pluginValidators, skipValidation),
new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker), new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker),
new AccountCommitValidator(repoManager, allUsers, accountValidator), new AccountCommitValidator(repoManager, allUsers, accountValidator),
new GroupCommitValidator(allUsers))); new GroupCommitValidator(allUsers)));
@@ -476,27 +477,50 @@ public class CommitValidators {
/** Execute commit validation plug-ins */ /** Execute commit validation plug-ins */
public static class PluginCommitValidationListener implements CommitValidationListener { public static class PluginCommitValidationListener implements CommitValidationListener {
private boolean skipValidation;
private final PluginSetContext<CommitValidationListener> commitValidationListeners; private final PluginSetContext<CommitValidationListener> commitValidationListeners;
public PluginCommitValidationListener( public PluginCommitValidationListener(
final PluginSetContext<CommitValidationListener> commitValidationListeners) { final PluginSetContext<CommitValidationListener> commitValidationListeners) {
this(commitValidationListeners, false);
}
public PluginCommitValidationListener(
final PluginSetContext<CommitValidationListener> commitValidationListeners,
boolean skipValidation) {
this.skipValidation = skipValidation;
this.commitValidationListeners = commitValidationListeners; this.commitValidationListeners = commitValidationListeners;
} }
private void runValidator(
CommitValidationListener validator,
List<CommitValidationMessage> messages,
CommitReceivedEvent receiveEvent)
throws CommitValidationException {
if (skipValidation && !validator.shouldValidateAllCommits()) {
return;
}
messages.addAll(validator.onCommitReceived(receiveEvent));
}
@Override @Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException { throws CommitValidationException {
List<CommitValidationMessage> messages = new ArrayList<>(); List<CommitValidationMessage> messages = new ArrayList<>();
try { try {
commitValidationListeners.runEach( commitValidationListeners.runEach(
l -> messages.addAll(l.onCommitReceived(receiveEvent)), l -> runValidator(l, messages, receiveEvent), CommitValidationException.class);
CommitValidationException.class);
} catch (CommitValidationException e) { } catch (CommitValidationException e) {
messages.addAll(e.getMessages()); messages.addAll(e.getMessages());
throw new CommitValidationException(e.getMessage(), messages); throw new CommitValidationException(e.getMessage(), messages);
} }
return messages; return messages;
} }
@Override
public boolean shouldValidateAllCommits() {
return commitValidationListeners.stream().anyMatch(v -> v.shouldValidateAllCommits());
}
} }
public static class SignedOffByValidator implements CommitValidationListener { public static class SignedOffByValidator implements CommitValidationListener {

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.server.plugincontext.PluginContext.PluginMetrics;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.util.Iterator; import java.util.Iterator;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.stream.Stream;
/** /**
* Context to invoke extensions from a {@link DynamicSet}. * Context to invoke extensions from a {@link DynamicSet}.
@@ -147,6 +148,10 @@ public class PluginSetContext<T> implements Iterable<PluginSetEntryContext<T>> {
.forEach(p -> PluginContext.runLogExceptions(pluginMetrics, p, extensionImplConsumer)); .forEach(p -> PluginContext.runLogExceptions(pluginMetrics, p, extensionImplConsumer));
} }
public Stream<T> stream() {
return dynamicSet.stream();
}
/** /**
* Invokes each extension in the set. All exceptions from the plugin extensions except exceptions * Invokes each extension in the set. All exceptions from the plugin extensions except exceptions
* of the specified type are caught and logged. * of the specified type are caught and logged.

View File

@@ -2258,7 +2258,7 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(messages).hasSize(1); assertThat(messages).hasSize(1);
Message msg = messages.get(0); Message msg = messages.get(0);
assertThat(msg.rcpt()).containsExactly(user.getEmailAddress()); assertThat(msg.rcpt()).containsExactly(user.getEmailAddress());
assertThat(msg.body()).contains(admin.fullName() + " has removed a vote on this change.\n"); assertThat(msg.body()).contains(admin.fullName() + " has removed a vote from this change.");
assertThat(msg.body()) assertThat(msg.body())
.contains("Removed Code-Review+1 by " + user.fullName() + " <" + user.email() + ">\n"); .contains("Removed Code-Review+1 by " + user.fullName() + " <" + user.email() + ">\n");

View File

@@ -93,7 +93,6 @@ import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.events.CommitReceivedEvent; import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.receive.NoteDbPushOption; import com.google.gerrit.server.git.receive.NoteDbPushOption;
import com.google.gerrit.server.git.receive.ReceiveConstants; import com.google.gerrit.server.git.receive.ReceiveConstants;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidationListener; import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.CommitValidationMessage; import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.CommitValidators.ChangeIdValidator; import com.google.gerrit.server.git.validators.CommitValidators.ChangeIdValidator;
@@ -2293,14 +2292,27 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
private static class TestValidator implements CommitValidationListener { private static class TestValidator implements CommitValidationListener {
private final AtomicInteger count = new AtomicInteger(); private final AtomicInteger count = new AtomicInteger();
private final boolean validateAll;
TestValidator(boolean validateAll) {
this.validateAll = validateAll;
}
TestValidator() {
this(false);
}
@Override @Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) {
throws CommitValidationException {
count.incrementAndGet(); count.incrementAndGet();
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public boolean shouldValidateAllCommits() {
return validateAll;
}
public int count() { public int count() {
return count.get(); return count.get();
} }
@@ -2311,6 +2323,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
String master = "refs/heads/master"; String master = "refs/heads/master";
TestValidator validator = new TestValidator(); TestValidator validator = new TestValidator();
RegistrationHandle handle = commitValidators.add("test-validator", validator); RegistrationHandle handle = commitValidators.add("test-validator", validator);
RegistrationHandle handle2 = null;
try { try {
// Validation listener is called on normal push // Validation listener is called on normal push
@@ -2337,8 +2350,25 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
r = push3.to(master); r = push3.to(master);
r.assertOkStatus(); r.assertOkStatus();
assertThat(validator.count()).isEqualTo(1); assertThat(validator.count()).isEqualTo(1);
// Validation listener that needs to validate all commits gets called even
// when the skip option is used.
TestValidator validator2 = new TestValidator(true);
handle2 = commitValidators.add("test-validator-2", validator2);
PushOneCommit push4 =
pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
push4.setPushOptions(ImmutableList.of(PUSH_OPTION_SKIP_VALIDATION));
r = push4.to(master);
r.assertOkStatus();
// First listener was not called; its count remains the same.
assertThat(validator.count()).isEqualTo(1);
// Second listener was called.
assertThat(validator2.count()).isEqualTo(1);
} finally { } finally {
handle.remove(); handle.remove();
if (handle2 != null) {
handle2.remove();
}
} }
} }

View File

@@ -0,0 +1,198 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.acceptance.GitUtil.deleteRef;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.events.RefReceivedEvent;
import com.google.gerrit.server.git.validators.RefOperationValidationListener;
import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.junit.Test;
public class RefOperationValidationIT extends AbstractDaemonTest {
private static final String TEST_REF = "refs/heads/protected";
@Inject DynamicSet<RefOperationValidationListener> validators;
private class TestRefValidator implements RefOperationValidationListener, AutoCloseable {
private final ReceiveCommand.Type rejectType;
private final String rejectRef;
private final RegistrationHandle handle;
public TestRefValidator(ReceiveCommand.Type rejectType) {
this.rejectType = rejectType;
this.rejectRef = TEST_REF;
this.handle = validators.add("test-" + rejectType.name(), this);
}
@Override
public List<ValidationMessage> onRefOperation(RefReceivedEvent refEvent)
throws ValidationException {
if (refEvent.getRefName().equals(rejectRef)
&& refEvent.command.getType().equals(rejectType)) {
throw new ValidationException(rejectType.name());
}
return Collections.emptyList();
}
@Override
public void close() throws Exception {
handle.remove();
}
}
@Test
public void rejectRefCreation() throws Exception {
try (TestRefValidator validator = new TestRefValidator(CREATE)) {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
assert_().fail("expected exception");
} catch (RestApiException expected) {
assertThat(expected).hasMessageThat().contains(CREATE.name());
}
}
@Test
public void rejectRefCreationByPush() throws Exception {
try (TestRefValidator validator = new TestRefValidator(CREATE)) {
grant(project, "refs/*", Permission.PUSH, true);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to("refs/heads/master");
r1.assertOkStatus();
PushOneCommit.Result r2 = push1.to(TEST_REF);
r2.assertErrorStatus(CREATE.name());
}
}
@Test
public void rejectRefDeletion() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
try (TestRefValidator validator = new TestRefValidator(DELETE)) {
gApi.projects().name(project.get()).branch(TEST_REF).delete();
assert_().fail("expected exception");
} catch (RestApiException expected) {
assertThat(expected).hasMessageThat().contains(DELETE.name());
}
}
@Test
public void rejectRefDeletionByPush() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
grant(project, "refs/*", Permission.DELETE, true);
try (TestRefValidator validator = new TestRefValidator(DELETE)) {
PushResult result = deleteRef(testRepo, TEST_REF);
RemoteRefUpdate refUpdate = result.getRemoteUpdate(TEST_REF);
assertThat(refUpdate.getMessage()).contains(DELETE.name());
}
}
@Test
public void rejectRefUpdateFastForward() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
try (TestRefValidator validator = new TestRefValidator(UPDATE)) {
grant(project, "refs/*", Permission.PUSH, true);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to(TEST_REF);
r1.assertErrorStatus(UPDATE.name());
}
}
@Test
public void rejectRefUpdateNonFastForward() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
try (TestRefValidator validator = new TestRefValidator(UPDATE_NONFASTFORWARD)) {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
grant(project, "refs/*", Permission.PUSH, true);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to(TEST_REF);
r1.assertOkStatus();
// Reset HEAD to initial so the new change is a non-fast forward
RefUpdate ru = repo().updateRef(HEAD);
ru.setNewObjectId(initial);
assertThat(ru.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED);
PushOneCommit push2 =
pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
push2.setForce(true);
PushOneCommit.Result r2 = push2.to(TEST_REF);
r2.assertErrorStatus(UPDATE_NONFASTFORWARD.name());
}
}
@Test
public void rejectRefUpdateNonFastForwardToExistingCommit() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
try (TestRefValidator validator = new TestRefValidator(UPDATE_NONFASTFORWARD)) {
grant(project, "refs/*", Permission.PUSH, true);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to("refs/heads/master");
r1.assertOkStatus();
ObjectId push1Id = r1.getCommit();
PushOneCommit push2 =
pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
PushOneCommit.Result r2 = push2.to("refs/heads/master");
r2.assertOkStatus();
ObjectId push2Id = r2.getCommit();
RefUpdate ru = repo().updateRef(HEAD);
ru.setNewObjectId(push1Id);
assertThat(ru.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED);
PushOneCommit push3 =
pushFactory.create(admin.newIdent(), testRepo, "change3", "c.txt", "content");
PushOneCommit.Result r3 = push3.to(TEST_REF);
r3.assertOkStatus();
ru = repo().updateRef(HEAD);
ru.setNewObjectId(push2Id);
assertThat(ru.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED);
PushOneCommit push4 =
pushFactory.create(admin.newIdent(), testRepo, "change4", "d.txt", "content");
push4.setForce(true);
PushOneCommit.Result r4 = push4.to(TEST_REF);
r4.assertErrorStatus(UPDATE_NONFASTFORWARD.name());
}
}
}

View File

@@ -74,7 +74,7 @@ public class DeleteVoteIT extends AbstractDaemonTest {
assertThat(messages).hasSize(1); assertThat(messages).hasSize(1);
FakeEmailSender.Message msg = messages.get(0); FakeEmailSender.Message msg = messages.get(0);
assertThat(msg.rcpt()).containsExactly(user.getEmailAddress()); assertThat(msg.rcpt()).containsExactly(user.getEmailAddress());
assertThat(msg.body()).contains(admin.fullName() + " has removed a vote on this change.\n"); assertThat(msg.body()).contains(admin.fullName() + " has removed a vote from this change.");
assertThat(msg.body()) assertThat(msg.body())
.contains("Removed Code-Review+1 by " + user.fullName() + " <" + user.email() + ">\n"); .contains("Removed Code-Review+1 by " + user.fullName() + " <" + user.email() + ">\n");

View File

@@ -37,23 +37,23 @@ public class ElasticContainer extends ElasticsearchContainer {
private static String getImageName(ElasticVersion version) { private static String getImageName(ElasticVersion version) {
switch (version) { switch (version) {
case V5_6: case V5_6:
return "docker.elastic.co/elasticsearch/elasticsearch:5.6.16"; return "blacktop/elasticsearch:5.6.16";
case V6_2: case V6_2:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4"; return "blacktop/elasticsearch:6.2.4";
case V6_3: case V6_3:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.2"; return "blacktop/elasticsearch:6.3.2";
case V6_4: case V6_4:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3"; return "blacktop/elasticsearch:6.4.3";
case V6_5: case V6_5:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.5.4"; return "blacktop/elasticsearch:6.5.4";
case V6_6: case V6_6:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.6.2"; return "blacktop/elasticsearch:6.6.2";
case V6_7: case V6_7:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.7.2"; return "blacktop/elasticsearch:6.7.2";
case V7_0: case V7_0:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:7.0.1"; return "blacktop/elasticsearch:7.0.1";
case V7_1: case V7_1:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:7.1.1"; return "blacktop/elasticsearch:7.1.1";
} }
throw new IllegalStateException("No tests for version: " + version.name()); throw new IllegalStateException("No tests for version: " + version.name());
} }

View File

@@ -45,6 +45,9 @@ public class ElasticVersionTest extends GerritBaseTests {
assertThat(ElasticVersion.forVersion("7.0.0")).isEqualTo(ElasticVersion.V7_0); assertThat(ElasticVersion.forVersion("7.0.0")).isEqualTo(ElasticVersion.V7_0);
assertThat(ElasticVersion.forVersion("7.0.1")).isEqualTo(ElasticVersion.V7_0); assertThat(ElasticVersion.forVersion("7.0.1")).isEqualTo(ElasticVersion.V7_0);
assertThat(ElasticVersion.forVersion("7.1.0")).isEqualTo(ElasticVersion.V7_1);
assertThat(ElasticVersion.forVersion("7.1.1")).isEqualTo(ElasticVersion.V7_1);
} }
@Test @Test
@@ -55,18 +58,6 @@ public class ElasticVersionTest extends GerritBaseTests {
ElasticVersion.forVersion("4.0.0"); ElasticVersion.forVersion("4.0.0");
} }
@Test
public void version6() throws Exception {
assertThat(ElasticVersion.V5_6.isV6OrLater()).isFalse();
assertThat(ElasticVersion.V6_2.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_3.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_4.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_5.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_6.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_7.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V7_0.isV6OrLater()).isTrue();
}
@Test @Test
public void atLeastMinorVersion() throws Exception { public void atLeastMinorVersion() throws Exception {
assertThat(ElasticVersion.V5_6.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse(); assertThat(ElasticVersion.V5_6.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse();
@@ -77,10 +68,24 @@ public class ElasticVersionTest extends GerritBaseTests {
assertThat(ElasticVersion.V6_6.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse(); assertThat(ElasticVersion.V6_6.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse();
assertThat(ElasticVersion.V6_7.isAtLeastMinorVersion(ElasticVersion.V6_7)).isTrue(); assertThat(ElasticVersion.V6_7.isAtLeastMinorVersion(ElasticVersion.V6_7)).isTrue();
assertThat(ElasticVersion.V7_0.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse(); assertThat(ElasticVersion.V7_0.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse();
assertThat(ElasticVersion.V7_1.isAtLeastMinorVersion(ElasticVersion.V6_7)).isFalse();
} }
@Test @Test
public void version7() throws Exception { public void version6OrLater() throws Exception {
assertThat(ElasticVersion.V5_6.isV6OrLater()).isFalse();
assertThat(ElasticVersion.V6_2.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_3.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_4.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_5.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_6.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V6_7.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V7_0.isV6OrLater()).isTrue();
assertThat(ElasticVersion.V7_1.isV6OrLater()).isTrue();
}
@Test
public void version7OrLater() throws Exception {
assertThat(ElasticVersion.V5_6.isV7OrLater()).isFalse(); assertThat(ElasticVersion.V5_6.isV7OrLater()).isFalse();
assertThat(ElasticVersion.V6_2.isV7OrLater()).isFalse(); assertThat(ElasticVersion.V6_2.isV7OrLater()).isFalse();
assertThat(ElasticVersion.V6_3.isV7OrLater()).isFalse(); assertThat(ElasticVersion.V6_3.isV7OrLater()).isFalse();
@@ -89,5 +94,6 @@ public class ElasticVersionTest extends GerritBaseTests {
assertThat(ElasticVersion.V6_6.isV7OrLater()).isFalse(); assertThat(ElasticVersion.V6_6.isV7OrLater()).isFalse();
assertThat(ElasticVersion.V6_7.isV7OrLater()).isFalse(); assertThat(ElasticVersion.V6_7.isV7OrLater()).isFalse();
assertThat(ElasticVersion.V7_0.isV7OrLater()).isTrue(); assertThat(ElasticVersion.V7_0.isV7OrLater()).isTrue();
assertThat(ElasticVersion.V7_1.isV7OrLater()).isTrue();
} }
} }

View File

@@ -265,7 +265,7 @@ fi
if test -z "$JAVA" ; then if test -z "$JAVA" ; then
echo >&2 "Cannot find a JRE or JDK. Please ensure that the JAVA_HOME environment" echo >&2 "Cannot find a JRE or JDK. Please ensure that the JAVA_HOME environment"
echo >&2 "variable or container.javaHome in $GERRIT_SITE/etc/gerrit.config is" echo >&2 "variable or container.javaHome in $GERRIT_SITE/etc/gerrit.config is"
echo >&2 "set to a valid >=1.7 JRE location" echo >&2 "set to a valid >=1.8 JRE location"
exit 1 exit 1
fi fi

View File

@@ -23,8 +23,9 @@
{template .DeleteVote kind="text"} {template .DeleteVote kind="text"}
{@param change: ?} {@param change: ?}
{@param coverLetter: ?} {@param coverLetter: ?}
{@param email: ?}
{@param fromName: ?} {@param fromName: ?}
{$fromName} has removed a vote on this change.{\n} {$fromName} has removed a vote from this change.{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}{\n}
{\n} {\n}
Change subject: {$change.subject}{\n} Change subject: {$change.subject}{\n}
......................................................................{\n} ......................................................................{\n}

View File

@@ -53,7 +53,7 @@
{/if} {/if}
{else} {else}
{$ownerName} has uploaded this change for review. {$ownerName} has uploaded this change for review.
{if $email.changeUrl} ( {$email.changeUrl}{/if} {if $email.changeUrl} ( {$email.changeUrl} ){/if}
{/if}{\n} {/if}{\n}
{\n} {\n}

View File

@@ -17,8 +17,8 @@
def _impl(ctx): def _impl(ctx):
zip_output = ctx.outputs.zip zip_output = ctx.outputs.zip
transitive_jars = depset(transitive = [l.java.transitive_deps for l in ctx.attr.libs]) transitive_jars = depset(transitive = [j.java.transitive_deps for j in ctx.attr.libs])
source_jars = depset(transitive = [l.java.source_jars for l in ctx.attr.libs]) source_jars = depset(transitive = [j.java.source_jars for j in ctx.attr.libs])
transitive_jar_paths = [j.path for j in transitive_jars.to_list()] transitive_jar_paths = [j.path for j in transitive_jars.to_list()]
dir = ctx.outputs.zip.path + ".dir" dir = ctx.outputs.zip.path + ".dir"

View File

@@ -28,8 +28,8 @@ public class %s {}
_PREFIXES = ("org", "com", "edu") _PREFIXES = ("org", "com", "edu")
def _SafeIndex(l, val): def _SafeIndex(j, val):
for i, v in enumerate(l): for i, v in enumerate(j):
if val == v: if val == v:
return i return i
return -1 return -1

View File

@@ -76,11 +76,11 @@ def _war_impl(ctx):
# Add lib # Add lib
transitive_libs = [] transitive_libs = []
for l in ctx.attr.libs: for j in ctx.attr.libs:
if hasattr(l, "java"): if hasattr(j, "java"):
transitive_libs.append(l.java.transitive_runtime_deps) transitive_libs.append(j.java.transitive_runtime_deps)
elif hasattr(l, "files"): elif hasattr(j, "files"):
transitive_libs.append(l.files) transitive_libs.append(j.files)
transitive_lib_deps = depset(transitive = transitive_libs) transitive_lib_deps = depset(transitive = transitive_libs)
for dep in transitive_lib_deps.to_list(): for dep in transitive_lib_deps.to_list():
@@ -89,8 +89,8 @@ def _war_impl(ctx):
# Add pgm lib # Add pgm lib
transitive_pgmlibs = [] transitive_pgmlibs = []
for l in ctx.attr.pgmlibs: for j in ctx.attr.pgmlibs:
transitive_pgmlibs.append(l.java.transitive_runtime_deps) transitive_pgmlibs.append(j.java.transitive_runtime_deps)
transitive_pgmlib_deps = depset(transitive = transitive_pgmlibs) transitive_pgmlib_deps = depset(transitive = transitive_pgmlibs)
for dep in transitive_pgmlib_deps.to_list(): for dep in transitive_pgmlib_deps.to_list():