| // 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(); |
| } |
| } |