Document global-refdb library
- Document all public classes, methods and interfaces
- Document configuration in config.md file
- Document bindings in bindings.md file
Feature: Issue 13978
Change-Id: I1309dc5688e2dbfed88babd25ae43fba0753943b
diff --git a/README.md b/README.md
index e1da49c..582bd2c 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,16 @@
Global ref-database interface for use with Gerrit Code Review.
-Enables the de-coupling between Gerrit, the multi-site plugin and the different
+Enables the de-coupling between Gerrit, its libModules and the different
implementations of a globally shared ref-database.
## Design
-The design for a global ref-db interface for Gerrit has been
-[posted to the Gerrit Code Review project](https://gerrit-review.googlesource.com/c/homepage/+/237980)
-for review.
+[The design for a global ref-db interface](https://gerrit.googlesource.com/plugins/multi-site/+/refs/heads/master/DESIGN.md#global-ref_db-plugin)
+can be found as part of the multi-site design documentation, where it first
+originated and was approved by the community.
+
+## Bindings
+
+In order to consume this library, some Guice bindings need to be registered
+appropriately. More information in the relevant [documentation](./bindings.md).
\ No newline at end of file
diff --git a/bindings.md b/bindings.md
new file mode 100644
index 0000000..920b911
--- /dev/null
+++ b/bindings.md
@@ -0,0 +1,70 @@
+
+Bindings
+=========================
+
+This library expects consumers to register Guice bindings.
+Some sensible implementations are already been provided and they must be bound
+by the Gerrit libModule in order to work.
+
+Alternatively libModules can decide to provide their own implementations (or
+override existing ones) and bound those instead.
+
+This is an example of binding that would allow consuming libModules getting up and
+running with this library:
+
+```java
+public class FooModule extends FactoryModule {
+
+ private final Configuration cfg;
+
+ public FooModule(Configuration cfg) {
+ this.cfg = cfg;
+ }
+
+ @Override
+ protected void configure() {
+ bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
+ bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+ factory(LockWrapper.Factory.class);
+ factory(SharedRefDbRepository.Factory.class);
+ factory(SharedRefDbRefDatabase.Factory.class);
+ factory(SharedRefDbRefUpdate.Factory.class);
+ factory(SharedRefDbBatchRefUpdate.Factory.class);
+ factory(RefUpdateValidator.Factory.class);
+ factory(BatchRefUpdateValidator.Factory.class);
+ bind(SharedRefDbConfiguration.class).toInstance(cfg.getSharedRefDbConfiguration());
+ bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class);
+ if (cfg.getSharedRefDbConfiguration().getSharedRefDb().getEnforcementRules().isEmpty()) {
+ bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
+ } else {
+ bind(SharedRefEnforcement.class)
+ .to(CustomSharedRefEnforcementByProject.class)
+ .in(Scopes.SINGLETON);
+ }
+ }
+}
+```
+
+## Ignored Refs - Optional
+
+Consumers of this library can specify an optional set of refs that should not
+be validated against the shared ref-db, this list should be provided via Guice
+named binding, for example:
+
+```java
+
+public class FooModule extends AbstractModule {
+
+@Override
+ protected void configure() {
+ // other bindings ...
+ bind(new TypeLiteral<ImmutableSet<String>>() {})
+ .annotatedWith(Names.named(SharedRefDbGitRepositoryManager.IGNORED_REFS))
+ .toInstance(
+ ImmutableSet.of(
+ "refs/foo/bar",
+ "refs/foo/baz"));
+ // other bindings ...
+ }
+}
+```
\ No newline at end of file
diff --git a/config.md b/config.md
new file mode 100644
index 0000000..21064be
--- /dev/null
+++ b/config.md
@@ -0,0 +1,60 @@
+
+global-refdb Configuration
+=========================
+
+Configuration should be specified in the `$site_path/etc/<libModule>.config` file of
+the libModule consuming this library.
+
+## Configuration parameters
+
+```ref-database.enabled```
+: Enable the use of a global refdb
+ Defaults: true
+
+```ref-database.enforcementRules.<policy>```
+: Level of consistency enforcement across sites on a project:refs basis.
+ Supports two values for enforcing the policy on multiple projects or refs.
+ If the project or ref is omitted, apply the policy to all projects or all refs.
+
+ The <policy> can have one of the following values:
+
+ 1. REQUIRED - Throw an exception if a git ref-update is processed against
+ a local ref not yet in sync with the global refdb.
+ The user transaction is cancelled.
+
+ 2. IGNORED - Ignore any validation against the global refdb.
+
+ *Example:*
+ ```
+ [ref-database "enforcementRules"]
+ IGNORED = AProject:/refs/heads/feature
+ ```
+
+ Ignore the alignment with the global refdb for AProject on refs/heads/feature.
+
+ Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
+
+```projects.pattern```
+: Specifies which projects should be validated against the global refdb.
+ It can be provided more than once, and supports three formats: regular
+ expressions, wildcard matching, and single project matching. All three
+ formats match case-sensitive.
+
+ Values starting with a caret `^` are treated as regular
+ expressions. For the regular expressions details please follow
+ official [java documentation](https://docs.oracle.com/javase/tutorial/essential/regex/).
+
+ Please note that regular expressions could also be used
+ with inverse match.
+
+ Values that are not regular expressions and end in `*` are
+ treated as wildcard matches. Wildcards match projects whose
+ name agrees from the beginning until the trailing `*`. So
+ `foo/b*` would match the projects `foo/b`, `foo/bar`, and
+ `foo/baz`, but neither `foobar`, nor `bar/foo/baz`.
+
+ Values that are neither regular expressions nor wildcards are
+ treated as single project matches. So `foo/bar` matches only
+ the project `foo/bar`, but no other project.
+
+ By default, all projects are matched.
\ No newline at end of file
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
index d1b6d53..dbc52e8 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
@@ -65,6 +65,7 @@
* @param refName to store the value for.
* @param currValue current expected value in the DB.
* @param newValue new value to store.
+ * @param <T> Type of the current and new value
* @return true if the put was successful; false otherwise.
* @throws GlobalRefDbSystemError the reference cannot be put due to a system error.
*/
@@ -84,8 +85,8 @@
/**
* Verify if the DB contains a value for the specific project and ref name
*
- * @param project
- * @param refName
+ * @param project name of the project containing the ref
+ * @param refName the name of the ref to check existence for
* @return true if the ref exists on the project
*/
boolean exists(Project.NameKey project, String refName);
@@ -104,6 +105,7 @@
* @param project project name
* @param refName reference name
* @param clazz wanted Class of the returned value
+ * @param <T> Type of the object associate to project and ref
* @return {@link java.util.Optional} of the value
* @throws GlobalRefDbSystemError value cannot be returned due to a system error.
*/
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbLockException.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbLockException.java
index 5bf44e5..fa757db 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbLockException.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbLockException.java
@@ -14,10 +14,21 @@
package com.gerritforge.gerrit.globalrefdb;
-/** Unable to lock a project/ref resource. */
+/**
+ * {@code GlobalRefDbLockException} is an exception that can be thrown when interacting with the
+ * global-refdb to represent the inability to lock or acquire a resource.
+ */
public class GlobalRefDbLockException extends RuntimeException {
private static final long serialVersionUID = 1L;
+ /**
+ * Constructs a new {@code GlobalRefDbLockException} with the specified project, refName and
+ * cause.
+ *
+ * @param project the project containing refName
+ * @param refName the specific ref for which the locking failed
+ * @param cause the cause of the locking failure
+ */
public GlobalRefDbLockException(String project, String refName, Exception cause) {
super(String.format("Unable to lock ref %s on project %s", refName, project), cause);
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbSystemError.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbSystemError.java
index 50dacd9..c470bff 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbSystemError.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDbSystemError.java
@@ -14,9 +14,19 @@
package com.gerritforge.gerrit.globalrefdb;
+/**
+ * {@code GlobalRefDbSystemError} is an exception that can be thrown when interacting with the
+ * global-refdb to represent any error in performing operations such as creating or deleting a ref.
+ */
public class GlobalRefDbSystemError extends RuntimeException {
private static final long serialVersionUID = 1L;
+ /**
+ * Constructs a new {@code GlobalRefDbSystemError} with the specified detail message and cause.
+ *
+ * @param msg the detail message
+ * @param cause the cause
+ */
public GlobalRefDbSystemError(String msg, Exception cause) {
super(msg, cause);
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
index 6c258ae..71d6621 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
@@ -14,6 +14,8 @@
package com.gerritforge.gerrit.globalrefdb.validation;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.OutOfSyncException;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
@@ -33,9 +35,11 @@
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.transport.ReceiveCommand;
+/** Enables the detection of out-of-sync by validating batch ref updates against the global refdb */
public class BatchRefUpdateValidator extends RefUpdateValidator {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ /** {@code BatchRefUpdateValidator} Factory for Guice assisted injection. */
public interface Factory {
BatchRefUpdateValidator create(
String projectName, RefDatabase refDb, ImmutableSet<String> ignoredRefs);
@@ -45,6 +49,23 @@
void apply(BatchRefUpdate batchRefUpdate, NoParameterVoidFunction arg) throws IOException;
}
+ /**
+ * Constructs a {@code BatchRefUpdateValidator} able to check the validity of batch ref-updates
+ * against global refdb before execution.
+ *
+ * @param sharedRefDb an instance of the global refdb to check for out-of-sync refs.
+ * @param validationMetrics to update validation results, such as split-brains.
+ * @param refEnforcement Specific ref enforcements for this project. Either a {@link
+ * CustomSharedRefEnforcementByProject} when custom policies are provided via configuration *
+ * file or a {@link DefaultSharedRefEnforcement} for defaults.
+ * @param lockWrapperFactory factory providing a {@link LockWrapper}
+ * @param projectsFilter filter to match whether the project being updated should be validated
+ * against global refdb
+ * @param projectName the name of the project being updated.
+ * @param refDb for ref operations
+ * @param ignoredRefs A set of refs for which updates should not be checked against the shared
+ * ref-db
+ */
@Inject
public BatchRefUpdateValidator(
SharedRefDatabaseWrapper sharedRefDb,
@@ -66,6 +87,24 @@
ignoredRefs);
}
+ /**
+ * Checks whether the provided batchRefUpdate should be validated first against the global refdb.
+ * If not it just execute the provided batchRefUpdateFunction. Upon success the batchRefUpdate is
+ * returned, upon failure split brain metrics are incremented and a {@link IOException} is thrown.
+ *
+ * <p>Validation is performed when either of these condition is true:
+ *
+ * <ul>
+ * <li>The project being updated is a global project ({@link
+ * RefUpdateValidator#isGlobalProject(String)}
+ * <li>The enforcement policy for the project being updated is {@link EnforcePolicy#IGNORED}
+ * </ul>
+ *
+ * @param batchRefUpdate batchRefUpdate object
+ * @param batchRefUpdateFunction batchRefUpdate function to execute upon validation
+ * @throws IOException batch update failed
+ */
+ @SuppressWarnings("JavadocReference")
public void executeBatchUpdateWithValidation(
BatchRefUpdate batchRefUpdate, NoParameterVoidFunction batchRefUpdateFunction)
throws IOException {
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModule.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModule.java
index 49b20d8..b8edc90 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModule.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModule.java
@@ -22,6 +22,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/** Binds {@link GlobalRefDatabase} to the {@link NoopSharedRefDatabase} */
public class LibModule extends LifecycleModule {
private static final Logger log = LoggerFactory.getLogger(LibModule.class);
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModuleLogFile.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModuleLogFile.java
index 075bcc9..c2858b4 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModuleLogFile.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LibModuleLogFile.java
@@ -20,8 +20,18 @@
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
+/** Abstract class to be extended by lib modules willing to create file loggers. */
public abstract class LibModuleLogFile {
+ /**
+ * Constructs an instance of {@code LibModuleLogFile} which creates an asynchronous appender with
+ * the provided logName and layout
+ *
+ * @param systemLog - to create the log appender
+ * @param logName - the name of the log file
+ * @param layout - the layout for the log
+ * @see org.apache.log4j.PatternLayout
+ */
public LibModuleLogFile(SystemLog systemLog, String logName, Layout layout) {
AsyncAppender asyncAppender = systemLog.createAsyncAppender(logName, layout, true, true);
Logger logger = LogManager.getLogger(logName);
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LockWrapper.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LockWrapper.java
index 18d75a3..a1312b0 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LockWrapper.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/LockWrapper.java
@@ -17,7 +17,9 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+/** Wrapper around an {@link AutoCloseable} lock to allow logging of resource releasing. */
public class LockWrapper implements AutoCloseable {
+ /** {@code LockWrapper} Factory for Guice assisted injection. */
public interface Factory {
LockWrapper create(
@Assisted("project") String project,
@@ -30,6 +32,15 @@
private final AutoCloseable lock;
private final SharedRefLogger sharedRefLogger;
+ /**
+ * Constructs a {@code LockWrapper} object for a specific refName of a project, which wraps a held
+ * lock, so that its details can be logged upon closure.
+ *
+ * @param sharedRefLogger to log the releasing of the lock
+ * @param project the project the lock has been acquired for
+ * @param refName the refName the lock has been acquired for
+ * @param lock the acquired lock
+ */
@Inject
public LockWrapper(
SharedRefLogger sharedRefLogger,
@@ -42,6 +53,11 @@
this.refName = refName;
}
+ /**
+ * Release the acquired lock and logs the lock release operation.
+ *
+ * @throws Exception if this resource cannot be closed
+ */
@Override
public void close() throws Exception {
lock.close();
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLogger.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLogger.java
index 5653ee2..7547cb7 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLogger.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLogger.java
@@ -40,6 +40,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ * Implementation of SharedRefLogger for Log4j. Logs to 'sharedref_log' file
+ *
+ * @see <a href="https://logging.apache.org/log4j/2.x/javadoc.html">log4j</a>
+ */
@Singleton
public class Log4jSharedRefLogger extends LibModuleLogFile implements SharedRefLogger {
private static final String LOG_NAME = "sharedref_log";
@@ -49,6 +54,14 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ /**
+ * Constructs a {@code Log4jSharedRefLogger} instance with the provided systemLog used to create
+ * the Log4J apppender and the repository manager to hydrate ref information with extra
+ * information, such as committer, before logging.
+ *
+ * @param systemLog systemLog to create log appender
+ * @param gitRepositoryManager to hydrate log entries with commit data
+ */
@Inject
public Log4jSharedRefLogger(SystemLog systemLog, GitRepositoryManager gitRepositoryManager) {
super(systemLog, LOG_NAME, new PatternLayout("[%d{ISO8601}] [%t] %-5p : %m%n"));
@@ -56,6 +69,15 @@
sharedRefDBLog = LoggerFactory.getLogger(LOG_NAME);
}
+ /**
+ * {@inheritDoc}.
+ *
+ * <p>Only logs commits and blob refs (throws {@link IncorrectObjectTypeException} for any other
+ * unexpected type). Additionally, it hydrates commits with information about the committer and
+ * commit message.
+ *
+ * <p>Logs Json serialization of {@link SharedRefLogEntry.UpdateRef}
+ */
@Override
public void logRefUpdate(String project, Ref currRef, ObjectId newRefValue) {
if (!ObjectId.zeroId().equals(newRefValue)) {
@@ -99,6 +121,12 @@
}
}
+ /**
+ * {@inheritDoc}.
+ *
+ * <p>Logs Json serialization of {@link SharedRefLogEntry.UpdateRef} or a {@link
+ * SharedRefLogEntry.DeleteRef}, when 'newRefValue' is null
+ */
@Override
public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {
if (newRefValue != null) {
@@ -112,16 +140,31 @@
}
}
+ /**
+ * {@inheritDoc}.
+ *
+ * <p>Logs Json serialization of {@link SharedRefLogEntry.DeleteProject}
+ */
@Override
public void logProjectDelete(String project) {
sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.DeleteProject(project)));
}
+ /**
+ * {@inheritDoc}.
+ *
+ * <p>Logs Json serialization of {@link SharedRefLogEntry.LockAcquire}
+ */
@Override
public void logLockAcquisition(String project, String refName) {
sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.LockAcquire(project, refName)));
}
+ /**
+ * {@inheritDoc}.
+ *
+ * <p>Logs Json serialization of {@link SharedRefLogEntry.LockRelease}
+ */
@Override
public void logLockRelease(String project, String refName) {
sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.LockRelease(project, refName)));
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectDeletedSharedDbCleanup.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectDeletedSharedDbCleanup.java
index ec72bcc..5a0cad7 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectDeletedSharedDbCleanup.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectDeletedSharedDbCleanup.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.inject.Inject;
+/** Removes a project from the global refdb upon deletion */
public class ProjectDeletedSharedDbCleanup implements ProjectDeletedListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -27,6 +28,13 @@
private final ValidationMetrics validationMetrics;
+ /**
+ * Constructs a {@code ProjectDeletedSharedDbCleanup} with the provided validation metrics and
+ * shared ref-database
+ *
+ * @param sharedDb global refdb used to validate project deletion
+ * @param validationMetrics to increase split-brain upon project failed validation
+ */
@Inject
public ProjectDeletedSharedDbCleanup(
SharedRefDatabaseWrapper sharedDb, ValidationMetrics validationMetrics) {
@@ -34,6 +42,13 @@
this.validationMetrics = validationMetrics;
}
+ /**
+ * Attempts to delete a project from the global refdb. Upon failure, it swallows {@link
+ * GlobalRefDbSystemError} exceptions and increments split brain metrics. Executed upon project
+ * deletion.
+ *
+ * @param event the project deletion event
+ */
@Override
public void onProjectDeleted(Event event) {
String projectName = event.getProjectName();
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectsFilter.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectsFilter.java
index 3121df4..c215e34 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectsFilter.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ProjectsFilter.java
@@ -27,11 +27,35 @@
// import com.google.gerrit.entities.AccessSection;
+/**
+ * Filter to match against project names to indicate whether a project should be validated against a
+ * global refdb.
+ *
+ * <p>Filters are computed by reading the configuration of the libModule consuming this library.
+ */
@Singleton
public class ProjectsFilter {
+
+ /** The type of the pattern defining this filter */
public enum PatternType {
+ /**
+ * Values starting with a caret `^` are treated as regular expressions. For the regular
+ * expressions details please @see <a
+ * href="https://docs.oracle.com/javase/tutorial/essential/regex/">official java
+ * documentation</a>
+ */
REGEX,
+ /**
+ * Values that are not regular expressions and end in `*` are treated as wildcard matches.
+ * Wildcards match projects whose name agrees from the beginning until the trailing `*`. So
+ * `foo/b*` would match the projects `foo/b`, `foo/bar`, and `foo/baz`, but neither `foobar`,
+ * nor `bar/foo/baz`.
+ */
WILDCARD,
+ /**
+ * Values that are neither regular expressions nor wildcards are treated as single project
+ * matches. So `foo/bar` matches only the project `foo/bar`, but no other project.
+ */
EXACT_MATCH;
public static PatternType getPatternType(String pattern) {
@@ -51,11 +75,24 @@
private Set<NameKey> localProjects = Sets.newConcurrentHashSet();
private final List<String> projectPatterns;
+ /**
+ * Constructs a {@code ProjectsFilter} by providing the libModule configuration
+ *
+ * @param cfg the libModule configuration
+ */
@Inject
public ProjectsFilter(SharedRefDbConfiguration cfg) {
projectPatterns = cfg.projects().getPatterns();
}
+ /**
+ * Given an event checks whether this project filter matches the project associated with the
+ * event. Returns false for all other events.
+ *
+ * @see #matches(NameKey)
+ * @param event the event to check against
+ * @return true when it matches, false otherwise.
+ */
public boolean matches(Event event) {
if (event == null) {
throw new IllegalArgumentException("Event object cannot be null");
@@ -66,10 +103,23 @@
return false;
}
+ /**
+ * checks whether this project filter matches the projectName
+ *
+ * @see #matches(NameKey)
+ * @param projectName the project name to check against
+ * @return true when it matches, false otherwise.
+ */
public boolean matches(String projectName) {
return matches(NameKey.parse(projectName));
}
+ /**
+ * checks whether this project filter matches the project name.
+ *
+ * @param name the name of the project to check
+ * @return true, when the project matches, false otherwise.
+ */
public boolean matches(Project.NameKey name) {
if (name == null || Strings.isNullOrEmpty(name.get())) {
throw new IllegalArgumentException(
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefPair.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefPair.java
index 7156a7e..4211c0c 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefPair.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefPair.java
@@ -17,11 +17,23 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+/**
+ * A convenience object encompassing the old (current) and the new (candidate) value of a {@link
+ * Ref}. This is used to snapshot the current status of a ref update so that validations against the
+ * global refdb are unaffected by changes on the {@link org.eclipse.jgit.lib.RefDatabase}.
+ */
public class RefPair {
public final Ref compareRef;
public final ObjectId putValue;
public final Exception exception;
+ /**
+ * Constructs a {@code RefPair} with the provided old and new ref values. The oldRef value is
+ * required not to be null, in which case an {@link IllegalArgumentException} is thrown.
+ *
+ * @param oldRef the old ref
+ * @param newRefValue the new (candidate) value for this ref.
+ */
RefPair(Ref oldRef, ObjectId newRefValue) {
if (oldRef == null) {
throw new IllegalArgumentException("Required not-null ref in RefPair");
@@ -31,16 +43,33 @@
this.exception = null;
}
+ /**
+ * Constructs a {@code RefPair} with the current ref and an Exception indicating why the new ref
+ * value failed being retrieved.
+ *
+ * @param newRef
+ * @param e
+ */
RefPair(Ref newRef, Exception e) {
this.compareRef = newRef;
this.exception = e;
this.putValue = ObjectId.zeroId();
}
+ /**
+ * Getter for the current ref
+ *
+ * @return the current ref value
+ */
public String getName() {
return compareRef.getName();
}
+ /**
+ * Whether the new value failed being retrieved
+ *
+ * @return true when this refPair has failed, false otherwise.
+ */
public boolean hasFailed() {
return exception != null;
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java
index cf93e1d..16dee20 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java
@@ -15,6 +15,8 @@
package com.gerritforge.gerrit.globalrefdb.validation;
import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.OutOfSyncException;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedDbSplitBrainException;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedLockException;
@@ -34,6 +36,7 @@
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate;
+/** Enables the detection of out-of-sync by validating ref updates against the global refdb. */
public class RefUpdateValidator {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -47,6 +50,7 @@
protected final ProjectsFilter projectsFilter;
private final ImmutableSet<String> ignoredRefs;
+ /** {@code RefUpdateValidator} Factory for Guice assisted injection. */
public interface Factory {
RefUpdateValidator create(
String projectName, RefDatabase refDb, ImmutableSet<String> ignoredRefs);
@@ -69,6 +73,23 @@
void invoke() throws IOException;
}
+ /**
+ * Constructs a {@code RefUpdateValidator} able to check the validity of ref-updates against a
+ * global refdb before execution.
+ *
+ * @param sharedRefDb an instance of the global refdb to check for out-of-sync refs.
+ * @param validationMetrics to update validation results, such as split-brains.
+ * @param refEnforcement Specific ref enforcements for this project. Either a {@link
+ * CustomSharedRefEnforcementByProject} when custom policies are provided via configuration
+ * file or a {@link DefaultSharedRefEnforcement} for defaults.
+ * @param lockWrapperFactory factory providing a {@link LockWrapper}
+ * @param projectsFilter filter to match whether the project being updated should be validated
+ * against global refdb
+ * @param projectName the name of the project being updated.
+ * @param refDb for ref operations
+ * @param ignoredRefs A set of refs for which updates should not be checked against the shared
+ * ref-db
+ */
@Inject
public RefUpdateValidator(
SharedRefDatabaseWrapper sharedRefDb,
@@ -89,6 +110,30 @@
this.projectsFilter = projectsFilter;
}
+ /**
+ * Checks whether the provided refUpdate should be validated first against the shared ref-db. If
+ * not it just execute the provided refUpdateFunction. If it should be validated against the
+ * global refdb then it does so by executing the {@link
+ * RefUpdateValidator#doExecuteRefUpdate(RefUpdate, NoParameterFunction)} first. Upon success the
+ * refUpdate is returned, upon failure split brain metrics are incremented and a {@link
+ * SharedDbSplitBrainException} is thrown.
+ *
+ * <p>Validation is performed when either of these condition is true
+ *
+ * <ul>
+ * <li>The ref being updated is not to be ignored ({@link
+ * RefUpdateValidator#isRefToBeIgnored(String)})
+ * <li>The project being updated is a global project ({@link
+ * RefUpdateValidator#isGlobalProject(String)}
+ * <li>The enforcement policy for the project being updated is {@link EnforcePolicy#IGNORED}
+ * </ul>
+ *
+ * @param refUpdate the refUpdate command
+ * @param refUpdateFunction the refUpdate function to execute after validation
+ * @return the result of the update, or "null" in case a split brain was detected but the policy
+ * enforcement was not REQUIRED
+ * @throws IOException Execution of ref update failed
+ */
public RefUpdate.Result executeRefUpdate(
RefUpdate refUpdate, NoParameterFunction<RefUpdate.Result> refUpdateFunction)
throws IOException {
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java
index d9c8051..4b5aeec 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java
@@ -26,6 +26,11 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+/**
+ * Wraps an instance of {@link GlobalRefDatabase} provided as {@link DynamicItem} via a Guice
+ * binding. Such instance is bound optionally and, in case no explicit binding is registered a
+ * {@link NoopSharedRefDatabase} instance is wrapped instead.
+ */
public class SharedRefDatabaseWrapper implements GlobalRefDatabase {
private static final GlobalRefDatabase NOOP_REFDB = new NoopSharedRefDatabase();
@@ -34,6 +39,12 @@
private final SharedRefLogger sharedRefLogger;
+ /**
+ * Constructs a {@code SharedRefDatabaseWrapper} wrapping an optional {@link GlobalRefDatabase},
+ * which might have been bound by consumers of this library.
+ *
+ * @param sharedRefLogger logger of shared ref-db operations.
+ */
@Inject
public SharedRefDatabaseWrapper(SharedRefLogger sharedRefLogger) {
this.sharedRefLogger = sharedRefLogger;
@@ -51,6 +62,7 @@
return sharedRefDb().isUpToDate(project, ref);
}
+ /** {@inheritDoc}. The operation is logged upon success. */
@Override
public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
throws GlobalRefDbSystemError {
@@ -61,6 +73,7 @@
return succeeded;
}
+ /** {@inheritDoc} the operation is logged upon success. */
@Override
public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
throws GlobalRefDbSystemError {
@@ -71,6 +84,7 @@
return succeeded;
}
+ /** {@inheritDoc}. The operation is logged. */
@Override
public AutoCloseable lockRef(Project.NameKey project, String refName)
throws GlobalRefDbLockException {
@@ -84,6 +98,7 @@
return sharedRefDb().exists(project, refName);
}
+ /** {@inheritDoc}. The operation is logged. */
@Override
public void remove(Project.NameKey project) throws GlobalRefDbSystemError {
sharedRefDb().remove(project);
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdate.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdate.java
index 8065fe8..d7c0c61 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdate.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdate.java
@@ -29,6 +29,10 @@
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.time.ProposedTimestamp;
+/**
+ * Batch of reference updates to be applied to a repository prior having checked the validity of
+ * those operations against the global refdb.
+ */
public class SharedRefDbBatchRefUpdate extends BatchRefUpdate {
private final BatchRefUpdate batchRefUpdate;
@@ -37,11 +41,22 @@
private final RefDatabase refDb;
private final ImmutableSet<String> ignoredRefs;
+ /** {@code SharedRefDbBatchRefUpdate} Factory for Guice assisted injection. */
public interface Factory {
SharedRefDbBatchRefUpdate create(
String project, RefDatabase refDb, ImmutableSet<String> ignoredRefs);
}
+ /**
+ * Constructs an instance of {@code SharedRefDbBatchRefUpdate} able to validate ref updates via a
+ * {@link BatchRefUpdateValidator}. It is basically a thin wrapper around {@link BatchRefUpdate},
+ * where the validity of the update operation is checked first against the global refdb instance.
+ *
+ * @param batchRefValidatorFactory Factory to provide {@link BatchRefUpdateValidator}
+ * @param project the project this batch update is for
+ * @param refDb a ref-database instance to create the underlying {@link BatchRefUpdate}
+ * @param ignoredRefs a set of refs that should not be validated against the global refdb
+ */
@Inject
public SharedRefDbBatchRefUpdate(
BatchRefUpdateValidator.Factory batchRefValidatorFactory,
@@ -166,6 +181,21 @@
return batchRefUpdate.addProposedTimestamp(ts);
}
+ /**
+ * Execute this batch update by delegating to the underlying {@link BatchRefUpdate} after checking
+ * first the validity of the operation via a {@link BatchRefUpdateValidator}.
+ *
+ * @param walk a RevWalk to parse tags in case the storage system wants to store them pre-peeled,
+ * a common performance optimization.
+ * @param monitor progress monitor to receive update status on.
+ * @param options a list of option strings; set null to execute without
+ * @throws IOException the database is unable to accept the update. This could be either caused by
+ * one of the underlying update command or by the batch ref validator proving that the current
+ * ref-db is not up-to-date with the global refdb
+ * @see BatchRefUpdate
+ * @see BatchRefUpdateValidator#executeBatchUpdateWithValidation(BatchRefUpdate,
+ * RefUpdateValidator.NoParameterVoidFunction)
+ */
@Override
public void execute(RevWalk walk, ProgressMonitor monitor, List<String> options)
throws IOException {
@@ -175,6 +205,20 @@
batchRefUpdate, () -> batchRefUpdate.execute(walk, monitor, options));
}
+ /**
+ * Execute this batch update by delegating to the underlying {@link BatchRefUpdate} after checking
+ * first the validity of the operation via a {@link BatchRefUpdateValidator}.
+ *
+ * @param walk a RevWalk to parse tags in case the storage system wants to store them pre-peeled,
+ * a common performance optimization.
+ * @param monitor progress monitor to receive update status on.
+ * @throws IOException the database is unable to accept the update. This could be either caused by
+ * one of the underlying update command or by the batch ref validator proving that the current
+ * ref-db is not up-to-date with the global refdb
+ * @see BatchRefUpdate
+ * @see BatchRefUpdateValidator#executeBatchUpdateWithValidation(BatchRefUpdate,
+ * RefUpdateValidator.NoParameterVoidFunction)
+ */
@Override
public void execute(RevWalk walk, ProgressMonitor monitor) throws IOException {
batchRefValidatorFactory
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java
index 1d5e023..a607cdc 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Suppliers.memoize;
import static com.google.common.base.Suppliers.ofInstance;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
@@ -30,6 +31,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ * Represents a configuration for the shared ref-database. This configuration is retrieved from the
+ * configuration file of the libModule is consuming the library.
+ */
public class SharedRefDbConfiguration {
private static final Logger log = LoggerFactory.getLogger(SharedRefDbConfiguration.class);
@@ -37,6 +42,13 @@
private final Supplier<SharedRefDatabase> sharedRefDb;
private final String pluginName;
+ /**
+ * Constructs a {@code SharedRefDbConfiguration} by providing the libModule name and a 'config'
+ * object
+ *
+ * @param config the libModule configuration
+ * @param pluginName the name of the libModule consuming this library
+ */
public SharedRefDbConfiguration(Config config, String pluginName) {
Supplier<Config> lazyCfg = lazyLoad(config);
projects = memoize(() -> new Projects(lazyCfg));
@@ -44,14 +56,20 @@
this.pluginName = pluginName;
}
+ /**
+ * @return the {@link SharedRefDatabase} computed from the configuration libModule configuration
+ * file
+ */
public SharedRefDatabase getSharedRefDb() {
return sharedRefDb.get();
}
+ /** @return Getter of projects checked against the global refdb */
public Projects projects() {
return projects.get();
}
+ /** @return name of the libModule consuming this library */
public String pluginName() {
return pluginName;
}
@@ -74,6 +92,12 @@
return ofInstance(config);
}
+ /**
+ * Represents the global refdb configuration, which is computed by reading the 'ref-database'
+ * section from the configuration file of this library's consumers. It allows to specify whether
+ * it is enabled, specific {@link SharedRefEnforcement}s and to tune other parameters that define
+ * specific behaviours of the global refdb.
+ */
public static class SharedRefDatabase {
public static final String SECTION = "ref-database";
public static final String ENABLE_KEY = "enabled";
@@ -91,10 +115,33 @@
}
}
+ /**
+ * Whether the use of the global refdb is enabled. Defaults 'false'
+ *
+ * @return true when enabled, false otherwise
+ */
public boolean isEnabled() {
return enabled;
}
+ /**
+ * Getter for the map of {@link EnforcePolicy} to a specific "project:refs". Each entry can be
+ * either be {@link SharedRefEnforcement.EnforcePolicy#IGNORED} or {@link
+ * SharedRefEnforcement.EnforcePolicy#REQUIRED} and it represents the level of consistency
+ * enforcements for that specific "project:refs". If the project or ref is omitted, apply the
+ * policy to all projects or all refs.
+ *
+ * <p>The projec/ref will not be validated against the global refdb if it one to be ignored by
+ * default ({@link SharedRefEnforcement#isRefToBeIgnoredBySharedRefDb(String)} or if it has been
+ * configured so, for example:
+ *
+ * <pre>
+ * [ref-database "enforcementRules"]
+ * IGNORED = AProject:/refs/heads/feature
+ * </pre>
+ *
+ * @return Map of "project:refs" policies
+ */
public Multimap<EnforcePolicy, String> getEnforcementRules() {
return enforcementRules;
}
@@ -105,15 +152,31 @@
}
}
+ /**
+ * Represents a set of projects for which ref updates operations should be validated against the
+ * global refdb. The list is computed from the consuming libModule's configuration file by looking
+ * at the "project.pattern" section. By defaults all projects are matched.
+ */
public static class Projects {
public static final String SECTION = "projects";
public static final String PATTERN_KEY = "pattern";
public List<String> patterns;
+ /**
+ * Constructs a {@code Projects} object by reading the list of "projects.pattern" possibly
+ * specified in the consuming libModule's configuration file.
+ *
+ * @param cfg the libModule's configuration supplier
+ */
public Projects(Supplier<Config> cfg) {
patterns = ImmutableList.copyOf(cfg.get().getStringList(SECTION, null, PATTERN_KEY));
}
+ /**
+ * The list of project patterns read from the consuming libModule's configuration file.
+ *
+ * @return list of project patterns.
+ */
public List<String> getPatterns() {
return patterns;
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbGitRepositoryManager.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbGitRepositoryManager.java
index c8e7053..4f13d6e 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbGitRepositoryManager.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbGitRepositoryManager.java
@@ -27,9 +27,28 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
+/**
+ * Implements a {@link GitRepositoryManager} interface with the intent of managing repository access
+ * and repository creation through instances of {@link SharedRefDbRepository}, which can therefore
+ * validate operations on the repository itself against the global refdb
+ */
@Singleton
public class SharedRefDbGitRepositoryManager implements GitRepositoryManager {
+ /**
+ * This value must be used for the named injection binding provided by libModules that want to
+ * define refs that should not be validated against the global refdb. For example:
+ *
+ * <pre>
+ * bind(new TypeLiteral<ImmutableSet<String>>() {})
+ * .annotatedWith(Names.named(SharedRefDbGitRepositoryManager.IGNORED_REFS))
+ * .toInstance(
+ * ImmutableSet.of(
+ * "refs/foo/bar",
+ * "refs/foo/baz"));
+ * </pre>
+ */
public static final String IGNORED_REFS = "ignored_refs";
+
private final GitRepositoryManager gitRepositoryManager;
private final SharedRefDbRepository.Factory sharedRefDbRepoFactory;
@@ -37,6 +56,15 @@
@Named(IGNORED_REFS)
private ImmutableSet<String> ignoredRefs = ImmutableSet.of();
+ /**
+ * Constructs a {@code SharedRefDbGitRepositoryManager} that can create and open Git repositories
+ * by wrapping them in a {@code SharedRefDbRepository} object, so that operations on them can be
+ * validated against a global refdb
+ *
+ * @param sharedRefDbRepoFactory a factory providing a {@link SharedRefDbRepository} instance
+ * @param localDiskRepositoryManager an instance to manage repositories stored on the local file
+ * system
+ */
@Inject
public SharedRefDbGitRepositoryManager(
SharedRefDbRepository.Factory sharedRefDbRepoFactory,
@@ -45,12 +73,30 @@
this.gitRepositoryManager = localDiskRepositoryManager;
}
+ /**
+ * Get (or open) a {@link Repository} by name.
+ *
+ * @param name the repository name, relative to the base directory.
+ * @return the repository instance
+ * @throws RepositoryNotFoundException the name does not denote an existing repository.
+ * @throws IOException the name cannot be read as a repository.
+ */
@Override
public Repository openRepository(Project.NameKey name)
throws RepositoryNotFoundException, IOException {
return wrap(name, gitRepositoryManager.openRepository(name));
}
+ /**
+ * Create (and open) a {@link Repository} by name.
+ *
+ * @param name the repository name, relative to the base directory.
+ * @return the repository instance
+ * @throws RepositoryCaseMismatchException the name collides with an existing repository name, but
+ * only in case of a character within the name.
+ * @throws RepositoryNotFoundException the name is invalid.
+ * @throws IOException the repository cannot be created.
+ */
@Override
public Repository createRepository(Project.NameKey name)
throws RepositoryCaseMismatchException, RepositoryNotFoundException, IOException {
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java
index e3fde9c..8b51beb 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java
@@ -27,6 +27,11 @@
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
+/**
+ * Wraps an instance of {@link RefDatabase} with the intent of wrapping {@link RefUpdate} operations
+ * to instances of {@link SharedRefDbRefUpdate} in order to allow validation of those operation
+ * against a shared ref-database before actually executing them.
+ */
public class SharedRefDbRefDatabase extends RefDatabase {
private final SharedRefDbRefUpdate.Factory refUpdateFactory;
private final SharedRefDbBatchRefUpdate.Factory batchRefUpdateFactory;
@@ -34,11 +39,22 @@
private final RefDatabase refDatabase;
private final ImmutableSet<String> ignoredRefs;
+ /** {@code SharedRefDbRefDatabase} Factory for Guice assisted injection. */
public interface Factory {
SharedRefDbRefDatabase create(
String projectName, RefDatabase refDatabase, ImmutableSet<String> ignoredRefs);
}
+ /**
+ * Constructs a {@code SharedRefDbRefDatabase} by wrapping an underlying refDatabase, so that
+ * update refs operations can be validated against a shared ref-database.
+ *
+ * @param refUpdateFactory a factory to provide a {@link SharedRefDbRefUpdate}
+ * @param batchRefUpdateFactory a factory to provide a {@link SharedRefDbBatchRefUpdate}
+ * @param projectName the name of the project to perform Git operations on
+ * @param refDatabase the wrapped {@link RefDatabase}
+ * @param ignoredRefs a set of reference for which ref-db validation should not be executed.
+ */
@Inject
public SharedRefDbRefDatabase(
SharedRefDbRefUpdate.Factory refUpdateFactory,
@@ -83,6 +99,13 @@
return refDatabase.getConflictingNames(name);
}
+ /**
+ * Wrap a {@link RefUpdate} obtained by calling the underlying {@link RefDatabase} in a {@link
+ * SharedRefDbRefUpdate}
+ *
+ * @see RefDatabase#newUpdate(String, boolean)
+ * @throws java.io.IOException the reference cannot be accessed.
+ */
@Override
public RefUpdate newUpdate(String name, boolean detach) throws IOException {
return wrapRefUpdate(refDatabase.newUpdate(name, detach));
@@ -93,6 +116,13 @@
return refDatabase.newRename(fromName, toName);
}
+ /**
+ * Obtains a {@link SharedRefDbBatchRefUpdate} via the {@code BatchRefUpdate.Factory} invoked on
+ * the underlying {@link RefDatabase}, so that batch updates can be validated against the shared
+ * ref-db.
+ *
+ * @see RefDatabase#newUpdate(String, boolean)
+ */
@Override
public BatchRefUpdate newBatchUpdate() {
return batchRefUpdateFactory.create(projectName, refDatabase, ignoredRefs);
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefUpdate.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefUpdate.java
index a461d83..9935c8d 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefUpdate.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefUpdate.java
@@ -28,6 +28,10 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushCertificate;
+/**
+ * Creates, updates or deletes any reference after having checked the validity of this operation
+ * against the global refdb.
+ */
public class SharedRefDbRefUpdate extends RefUpdate {
protected final RefUpdate refUpdateBase;
@@ -35,6 +39,7 @@
private final RefUpdateValidator.Factory refValidatorFactory;
private final RefUpdateValidator refUpdateValidator;
+ /** {@code SharedRefDbRefUpdate} Factory for Guice assisted injection. */
public interface Factory {
SharedRefDbRefUpdate create(
String projectName,
@@ -43,6 +48,17 @@
ImmutableSet<String> ignoredRefs);
}
+ /**
+ * Constructs a {@code SharedRefDbRefUpdate} to create, update or delete a reference in project
+ * projectName after validating the validity of the operation by an instance of {@code
+ * RefUpdateValidator}, which is {@link Inject}ed by Guice.
+ *
+ * @param refValidatorFactory factory for {@link RefUpdateValidator}
+ * @param projectName the name of the project being updated
+ * @param refUpdate the wrapped ref update operation
+ * @param refDb the mapping between refs and object ids
+ * @param ignoredRefs a list of refs for which to ignore validation for.
+ */
@Inject
public SharedRefDbRefUpdate(
RefUpdateValidator.Factory refValidatorFactory,
@@ -96,21 +112,55 @@
return notImplementedException();
}
+ /**
+ * Update the ref to the new value, after checking its validity via {@link RefUpdateValidator}
+ *
+ * @return the result status of the update
+ * @throws IOException an error occurred when attempting to write the change, either for an
+ * unexpected cause or because the validation failed and a split-brain was encountered or
+ * prevented.
+ */
@Override
public Result update() throws IOException {
return refUpdateValidator.executeRefUpdate(refUpdateBase, refUpdateBase::update);
}
+ /**
+ * Update the ref to the new value, after checking its validity via {@link RefUpdateValidator}
+ *
+ * @param rev a RevWalk instance this update command can borrow to perform the merge test. The
+ * walk will be reset to perform the test.
+ * @return the result status of the update
+ * @throws IOException an error occurred when attempting to write the change, either for an
+ * unexpected cause or because the validation failed and a split-brain was encountered or
+ * prevented.
+ */
@Override
public Result update(RevWalk rev) throws IOException {
return refUpdateValidator.executeRefUpdate(refUpdateBase, () -> refUpdateBase.update(rev));
}
+ /**
+ * Delete the ref after checking its validity via {@link RefUpdateValidator}
+ *
+ * @return the result status of the delete
+ * @throws IOException an error occurred when attempting to write the change, either for an
+ * unexpected cause or because the validation failed and a split-brain was encountered or
+ * prevented.
+ */
@Override
public Result delete() throws IOException {
return refUpdateValidator.executeRefUpdate(refUpdateBase, refUpdateBase::delete);
}
+ /**
+ * Delete the ref after checking its validity via {@link RefUpdateValidator}
+ *
+ * @param walk a RevWalk instance this delete command can borrow to perform the merge test. The
+ * walk will be reset to perform the test.
+ * @return the result status of the delete
+ * @throws IOException deletion failed
+ */
@Override
public Result delete(RevWalk walk) throws IOException {
return refUpdateValidator.executeRefUpdate(refUpdateBase, () -> refUpdateBase.delete(walk));
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRepository.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRepository.java
index 0e428fe..9e8bb9b 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRepository.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRepository.java
@@ -23,15 +23,29 @@
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+/**
+ * Wrapper around a delegated Git repository {@link DelegateRepository}, which allows to intercept
+ * and proxy git operations through the global refdb to perform validation.
+ */
public class SharedRefDbRepository extends DelegateRepository {
private final SharedRefDbRefDatabase sharedRefDatabase;
+ /** {@code SharedRefDbRepository} Factory for Guice assisted injection. */
public interface Factory {
SharedRefDbRepository create(
String projectName, Repository repository, ImmutableSet<String> ignoredRefs);
}
+ /**
+ * Constructs a {@code SharedRefDbRepository} to validate git operation for projectName in {@code
+ * repository} against a shared refdb constructed via RefDBFactory
+ *
+ * @param refDbFactory a factory generating SharedRefDbRefDatabase ({@link Inject}ed by Guice)
+ * @param projectName the name of the project receiving the update
+ * @param repository the git repository
+ * @param ignoredRefs a set of references that do not need to be checked against the sared ref-db
+ */
@Inject
public SharedRefDbRepository(
SharedRefDbRefDatabase.Factory refDbFactory,
@@ -43,16 +57,40 @@
refDbFactory.create(projectName, repository.getRefDatabase(), ignoredRefs);
}
+ /**
+ * Getter for the shared ref database
+ *
+ * @return {@link RefDatabase} the shared ref database
+ */
@Override
public RefDatabase getRefDatabase() {
return sharedRefDatabase;
}
+ /**
+ * Create a command to update, create or delete ref in this repository.
+ *
+ * @param ref name of the ref the caller wants to modify.
+ * @return an update command. The caller must finish populating this command and then invoke one
+ * of the update methods to actually make a change.
+ * @throws java.io.IOException symbolic ref was passed in and could not be resolved back to the
+ * base ref, as the symbolic ref could not be read.
+ */
@Override
public RefUpdate updateRef(String ref) throws IOException {
return sharedRefDatabase.wrapRefUpdate(delegate.updateRef(ref));
}
+ /**
+ * Create a command to update, create or delete ref in this repository.
+ *
+ * @param ref name of the ref the caller wants to modify.
+ * @param detach true to create a detached head
+ * @return an update command. The caller must finish populating this command and then invoke one
+ * of the update methods to actually make a change.
+ * @throws java.io.IOException a symbolic ref was passed in and could not be resolved back to the
+ * base ref, as the symbolic ref could not be read.
+ */
@Override
public RefUpdate updateRef(String ref, boolean detach) throws IOException {
return sharedRefDatabase.wrapRefUpdate(delegate.updateRef(ref, detach));
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogEntry.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogEntry.java
index d580057..c72ab7c 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogEntry.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogEntry.java
@@ -17,7 +17,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.GitPerson;
-public class SharedRefLogEntry {
+class SharedRefLogEntry {
public enum Type {
UPDATE_REF,
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogger.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogger.java
index faef27b..d2b1822 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogger.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefLogger.java
@@ -19,13 +19,46 @@
public interface SharedRefLogger {
+ /**
+ * Log the update of currRef in project project to ref newRefValue
+ *
+ * @param project the project the update is for
+ * @param currRef the ref being updated
+ * @param newRefValue the new value of the ref being updated
+ */
void logRefUpdate(String project, Ref currRef, ObjectId newRefValue);
+ /**
+ * Log the update of currRef, pointed to by refName, in project 'project' to ref 'newRefValue'
+ *
+ * @param project the project the update is for
+ * @param refName the name of the ref being updatex
+ * @param currRef the current value of the ref being updated
+ * @param newRefValue the new value of the ref being updated
+ * @param <T> Type of the 'currRef' and the 'newRefValue'
+ */
<T> void logRefUpdate(String project, String refName, T currRef, T newRefValue);
+ /**
+ * Log the deletion of 'project' from the global refdb
+ *
+ * @param project the project being deleted
+ */
void logProjectDelete(String project);
+ /**
+ * Log the acquisition of a lock for the 'refName' of 'project'
+ *
+ * @param project the project containing the ref
+ * @param refName the name of the ref the lock is acquired for
+ */
void logLockAcquisition(String project, String refName);
+ /**
+ * Log the releasing of a previously acquired lock for the 'refName' of 'project'
+ *
+ * @param project the project containing the ref
+ * @param refName the name of the ref the lock is being releaed for
+ */
void logLockRelease(String project, String refName);
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationMetrics.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationMetrics.java
index 4cc10d2..583554c 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationMetrics.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationMetrics.java
@@ -22,6 +22,12 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+/**
+ * Creates and registers metrics related to ref-db validations, such as split brains events. Such
+ * metrics are exposed by Gerrit and can then be used for monitoring and alerting.
+ *
+ * <p>This class is a {@link Singleton} to ensure that metrics unique are only registered once.
+ */
@Singleton
public class ValidationMetrics {
private static final String GIT_UPDATE_SPLIT_BRAIN_PREVENTED = "git_update_split_brain_prevented";
@@ -30,6 +36,13 @@
private final Counter1<String> splitBrainPreventionCounter;
private final Counter1<String> splitBrainCounter;
+ /**
+ * Constructs a new {@code ValidationMetrics}, by passing metricMaker and the global refdb
+ * configuration. Both parameters are bound and {@link Inject}ed by Guice.
+ *
+ * @param metricMaker Factory to create metrics for monitoring
+ * @param cfg Configuration of shared ref-database configuration file
+ */
@Inject
public ValidationMetrics(MetricMaker metricMaker, SharedRefDbConfiguration cfg) {
this.splitBrainPreventionCounter =
@@ -49,10 +62,18 @@
"Ref-update operation left node in a split-brain scenario"));
}
+ /**
+ * Increment the "git_update_split_brain_prevented" metric counter to signal that a split-brain
+ * event was prevented by performing validation against the global refdb.
+ */
public void incrementSplitBrainPrevention() {
splitBrainPreventionCounter.increment(GIT_UPDATE_SPLIT_BRAIN_PREVENTED);
}
+ /**
+ * Increment the "git_update_split_brain" metric counter to signal that the current gerrit
+ * instance is in split-brain.
+ */
public void incrementSplitBrain() {
splitBrainCounter.increment(GIT_UPDATE_SPLIT_BRAIN);
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
index b1eca36..84f6a64 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
@@ -26,11 +26,21 @@
import java.util.Iterator;
import java.util.Map;
+/**
+ * Implementation of the {@link SharedRefEnforcement} interface which derives project and
+ * project/ref enforcement policy from the configuration of the libModule consuming this library
+ */
public class CustomSharedRefEnforcementByProject implements SharedRefEnforcement {
private static final String ALL = ".*";
private final Supplier<Map<String, Map<String, EnforcePolicy>>> predefEnforcements;
+ /**
+ * Constructs a {@code CustomSharedRefEnforcementByProject} with the values specified in the
+ * configuration of the libModule consuming this library
+ *
+ * @param config the libModule configuration
+ */
@Inject
public CustomSharedRefEnforcementByProject(SharedRefDbConfiguration config) {
this.predefEnforcements = memoize(() -> parseDryRunEnforcementsToMap(config));
@@ -71,6 +81,16 @@
return value.trim().isEmpty() ? ALL : value;
}
+ /**
+ * The enforcement policy for 'refName' in 'projectName' as computed from the libModule's
+ * configuration file.
+ *
+ * <p>By default all projects are REQUIRED to be consistent on all refs.
+ *
+ * @param projectName project to be enforced
+ * @param refName ref name to be enforced
+ * @return the enforcement policy for this project/ref
+ */
@Override
public EnforcePolicy getPolicy(String projectName, String refName) {
if (isRefToBeIgnoredBySharedRefDb(refName)) {
@@ -91,6 +111,14 @@
orDefault.getOrDefault(refName, orDefault.get(ALL)), EnforcePolicy.REQUIRED);
}
+ /**
+ * The enforcement policy for 'projectName' as computed from the libModule's configuration file.
+ *
+ * <p>By default all projects are REQUIRED to be consistent on all refs.
+ *
+ * @param projectName the name of the project to get the policy for
+ * @return the enforcement policy for the project
+ */
@Override
public EnforcePolicy getPolicy(String projectName) {
Map<String, EnforcePolicy> policiesForProject =
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java
index 9c2ef35..ecff62f 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java
@@ -14,13 +14,33 @@
package com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
+/**
+ * Default implementation of {@link SharedRefEnforcement}. This class provides the default
+ * project/ref enforcement rules when no more specific rules have been configured for the libModule
+ * consuming this library.
+ */
public class DefaultSharedRefEnforcement implements SharedRefEnforcement {
+ /**
+ * Returns {@link EnforcePolicy#IGNORED} for refs to be ignored {@link
+ * SharedRefEnforcement#isRefToBeIgnoredBySharedRefDb(String)}, {@link EnforcePolicy#REQUIRED}
+ * otherwise
+ *
+ * @param projectName project to be enforced
+ * @param refName ref name to be enforced
+ * @return the policy for this project/ref
+ */
@Override
public EnforcePolicy getPolicy(String projectName, String refName) {
return isRefToBeIgnoredBySharedRefDb(refName) ? EnforcePolicy.IGNORED : EnforcePolicy.REQUIRED;
}
+ /**
+ * The global refdb validation policy for 'projectName'
+ *
+ * @param projectName project to be enforced
+ * @return always {@link EnforcePolicy#REQUIRED}
+ */
@Override
public EnforcePolicy getPolicy(String projectName) {
return EnforcePolicy.REQUIRED;
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/NoopSharedRefDatabase.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/NoopSharedRefDatabase.java
index 4810c64..2a0f972 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/NoopSharedRefDatabase.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/NoopSharedRefDatabase.java
@@ -22,39 +22,105 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+/**
+ * Default implementation of the {@link GlobalRefDatabase} interface. accepts any refs without
+ * checking for consistency.
+ *
+ * <p>This is useful for setting up a test environment and allows multi-site library to be installed
+ * independently from any additional libModules or the existence of a specific Ref-DB installation.
+ */
public class NoopSharedRefDatabase implements GlobalRefDatabase {
+ /**
+ * Project/ref is always considered up-to-date
+ *
+ * @param project project name of the ref
+ * @param ref to be checked against global ref-db
+ * @return true
+ * @throws GlobalRefDbLockException Never thrown by this implementation
+ */
@Override
public boolean isUpToDate(Project.NameKey project, Ref ref) throws GlobalRefDbLockException {
return true;
}
+ /**
+ * Put is always considered successful
+ *
+ * @param project project name of the ref
+ * @param currRef old value to compare to.
+ * @param newRefValue new reference to store.
+ * @return true
+ * @throws GlobalRefDbSystemError Never thrown by this implementation
+ */
@Override
public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
throws GlobalRefDbSystemError {
return true;
}
+ /**
+ * Put is always considered unsuccessful
+ *
+ * @param project project name of the ref.
+ * @param refName to store the value for.
+ * @param currValue current expected value in the DB.
+ * @param newValue new value to store.
+ * @param <T> Type of the current and new value
+ * @return false
+ * @throws GlobalRefDbSystemError Never thrown by this implementation
+ */
@Override
public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
throws GlobalRefDbSystemError {
return false;
}
+ /**
+ * Locking the ref does nothing, but return an dummy {@link java.io.Closeable}.
+ *
+ * @param project project name
+ * @param refName ref to lock
+ * @return a dummy {@link java.io.Closeable}.
+ * @throws GlobalRefDbLockException Never thrown by this implementation
+ */
@Override
public AutoCloseable lockRef(Project.NameKey project, String refName)
throws GlobalRefDbLockException {
return () -> {};
}
+ /**
+ * project/refs are always assumed to be new as to never be considered out-of-sync
+ *
+ * @param project project containing the ref
+ * @param refName the name of the ref
+ * @return false
+ */
@Override
public boolean exists(Project.NameKey project, String refName) {
return false;
}
+ /**
+ * Does nothing, the project is always considered to have been removed correctly from the shared
+ * ref-db.
+ *
+ * @param project project name
+ * @throws GlobalRefDbSystemError Never thrown by this implementation
+ */
@Override
public void remove(Project.NameKey project) throws GlobalRefDbSystemError {}
+ /**
+ * Always return an empty object as to never be considered existing in the global refdb.
+ *
+ * @param project project name
+ * @param refName reference name
+ * @param clazz wanted Class of the returned value
+ * @return {@link Optional#empty()}
+ * @throws GlobalRefDbSystemError Never thrown by this implementation
+ */
@Override
public <T> Optional<T> get(Project.NameKey project, String refName, Class<T> clazz)
throws GlobalRefDbSystemError {
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedDbSplitBrainException.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedDbSplitBrainException.java
index aece4cb..dada134 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedDbSplitBrainException.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedDbSplitBrainException.java
@@ -16,13 +16,25 @@
import java.io.IOException;
+/** Split-brain detected when trying to update a ref */
public class SharedDbSplitBrainException extends IOException {
private static final long serialVersionUID = 1L;
+ /**
+ * Constructs a {@code SharedDbSplitBrainException} with a message
+ *
+ * @param message details about the detected split brain
+ */
public SharedDbSplitBrainException(String message) {
super(message);
}
+ /**
+ * Constructs a {@code SharedDbSplitBrainException} with a 'message' and a 'cause'
+ *
+ * @param message details about the detected split brain
+ * @param cause the cause of the split brain detection
+ */
public SharedDbSplitBrainException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedLockException.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedLockException.java
index 69c3f33..5963da2 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedLockException.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedLockException.java
@@ -20,6 +20,14 @@
public class SharedLockException extends IOException {
private static final long serialVersionUID = 1L;
+ /**
+ * Constructs a {@code SharedLockException} exception with the cause of failing to lock a
+ * project/ref resource
+ *
+ * @param project the project the lock is being acquired for
+ * @param refName the ref the project is being acquired for
+ * @param cause the cause of the failure
+ */
public SharedLockException(String project, String refName, Exception cause) {
super(String.format("Unable to lock project %s on ref %s", project, refName), cause);
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java
index d2c1ed6..a765975 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java
@@ -33,15 +33,22 @@
/**
* Get the enforcement policy for a project
*
- * @param projectName
+ * @param projectName the name of the project
* @return the {@link EnforcePolicy} value
*/
public EnforcePolicy getPolicy(String projectName);
/**
- * Check if a refName should be ignored by shared Ref-Db
+ * Check if a refName should be ignored by global refdb. The Default behaviour is to ignore:
*
- * @param refName
+ * <ul>
+ * <li>refs/draft-comments :user-specific temporary storage that does not need to be seen by
+ * other users/sites
+ * <li>refs/changes/<non-meta>: those refs are immutable
+ * <li>refs/cache-automerge: these refs would be never replicated anyway
+ * </ul>
+ *
+ * @param refName the name of the ref to check
* @return true if ref should be ignored; false otherwise
*/
default boolean isRefToBeIgnoredBySharedRefDb(String refName) {