blob: 56d536adccb2a1adf8339389f62591c1c7b58131 [file] [log] [blame]
// Copyright (C) 2023 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.server.update.context;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Passes additional information about an operation to the {@code BatchRefUpdate#execute} method.
*
* <p>To pass the additional information {@link RefUpdateContext}, wraps a code into an open
* RefUpdateContext, e.g.:
*
* <pre>{@code
* try(RefUpdateContext ctx = RefUpdateContext.open(RefUpdateType.CHANGE_MODIFICATION)) {
* ...
* // some code which modifies a ref using BatchRefUpdate.execute method
* }
* }</pre>
*
* When the {@code BatchRefUpdate#execute} method is executed, it can get all opened contexts and
* use it for an additional actions, e.g. it can put it in the reflog.
*
* <p>The information provided by this class is used internally in google.
*
* <p>The InMemoryRepositoryManager file makes some validation to ensure that RefUpdateContext is
* used correctly within the code (see thee validateRefUpdateContext method).
*
* <p>The class includes only operations from open-source gerrit and can be extended (see {@code
* TestActionRefUpdateContext} for example how to extend it).
*/
public class RefUpdateContext implements AutoCloseable {
private static final ThreadLocal<Deque<RefUpdateContext>> current = new ThreadLocal<>();
/**
* List of possible ref-update types.
*
* <p>Items in this enum are not fine-grained; different actions are shared the same type (e.g.
* {@link #CHANGE_MODIFICATION} includes posting comments, change edits and attention set update).
*
* <p>It is expected, that each type of operation should include only specific ref(s); check the
* validateRefUpdateContext in InMemoryRepositoryManager for relation between RefUpdateType and
* ref name.
*/
public enum RefUpdateType {
/**
* Indicates that the context is implemented as a descendant of the {@link RefUpdateContext} .
*
* <p>The {@link #getUpdateType()} returns this type for all descendant of {@link
* RefUpdateContext}. This type is never returned if the context is exactly {@link
* RefUpdateContext}.
*/
OTHER,
/**
* A ref is updated as a part of change-related operation.
*
* <p>This covers multiple different cases - creating and uploading changes and patchsets,
* comments operations, change edits, etc...
*/
CHANGE_MODIFICATION,
/** A ref is updated during merge-change operation. */
MERGE_CHANGE,
/** A ref is updated as a part of a repo sequence operation. */
REPO_SEQ,
/** A ref is updated as a part of a repo initialization. */
INIT_REPO,
/** A ref is udpated as a part of gpg keys modification. */
GPG_KEYS_MODIFICATION,
/** A ref is updated as a part of group(s) update */
GROUPS_UPDATE,
/** A ref is updated as a part of account(s) update. */
ACCOUNTS_UPDATE,
/** A ref is updated as a part of direct push. */
DIRECT_PUSH,
/** A ref is updated as a part of explicit branch or ref update operation. */
BRANCH_MODIFICATION,
/** A ref is updated as a part of explicit tag update operation. */
TAG_MODIFICATION,
/**
* A tag is updated as a part of an offline operation.
*
* <p>Offline operation - an operation which is executed separately from the gerrit server and
* can't be triggered by any gerrit API. E.g. schema update.
*/
OFFLINE_OPERATION,
/** A tag is updated as a part of an update-superproject flow. */
UPDATE_SUPERPROJECT,
/** A ref is updated as a part of explicit HEAD update operation. */
HEAD_MODIFICATION,
/** A ref is updated as a part of versioned meta data change. */
VERSIONED_META_DATA_CHANGE,
/** A ref is updated as a part of commit-ban operation. */
BAN_COMMIT,
/**
* A ref is updated inside a plugin.
*
* <p>If a plugin updates one of a special refs - it must also open a nested context.
*/
PLUGIN,
/** A ref is updated as a part of auto-close-changes. */
AUTO_CLOSE_CHANGES
}
/** Opens a provided context. */
protected static <T extends RefUpdateContext> T open(T ctx) {
getCurrent().addLast(ctx);
return ctx;
}
/** Opens a context of a give type. */
public static RefUpdateContext open(RefUpdateType updateType) {
checkArgument(updateType != RefUpdateType.OTHER, "The OTHER type is for internal use only.");
return open(new RefUpdateContext(updateType));
}
/** Returns the list of opened contexts; the first element is the outermost context. */
public static ImmutableList<RefUpdateContext> getOpenedContexts() {
return ImmutableList.copyOf(getCurrent());
}
/** Checks if there is an open context of the given type. */
public static boolean hasOpen(RefUpdateType type) {
return getCurrent().stream().anyMatch(ctx -> ctx.getUpdateType() == type);
}
private final RefUpdateType updateType;
private RefUpdateContext(RefUpdateType updateType) {
this.updateType = updateType;
}
protected RefUpdateContext() {
this(RefUpdateType.OTHER);
}
protected static final Deque<RefUpdateContext> getCurrent() {
Deque<RefUpdateContext> result = current.get();
if (result == null) {
result = new ArrayDeque<>();
current.set(result);
}
return result;
}
/**
* Returns the type of {@link RefUpdateContext}.
*
* <p>For descendants, always return {@link RefUpdateType#OTHER}
*/
public final RefUpdateType getUpdateType() {
return updateType;
}
/** Closes the current context. */
@Override
public void close() {
Deque<RefUpdateContext> openedContexts = getCurrent();
checkState(
openedContexts.peekLast() == this, "The current context is different from this context.");
openedContexts.removeLast();
}
}