Merge changes from topic "CachedPreferencesProto" * changes: CachedPreferences: change the cached format to CachedPreferencesProto CachedPreferences: add support for proto based preferences
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java index 9fec1fa..b17e5fc 100644 --- a/java/com/google/gerrit/server/account/AccountCacheImpl.java +++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -61,7 +61,7 @@ @Override protected void configure() { persist(BYID_AND_REV_NAME, CachedAccountDetails.Key.class, CachedAccountDetails.class) - .version(1) + .version(2) .keySerializer(CachedAccountDetails.Key.Serializer.INSTANCE) .valueSerializer(CachedAccountDetails.Serializer.INSTANCE) .loader(Loader.class);
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java index d591809..2b0ba3f 100644 --- a/java/com/google/gerrit/server/account/AccountConfig.java +++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -196,7 +196,7 @@ */ public CachedPreferences asCachedPreferences() { checkLoaded(); - return CachedPreferences.fromConfig(preferences.getRaw()); + return CachedPreferences.fromLegacyConfig(preferences.getRaw()); } @Override
diff --git a/java/com/google/gerrit/server/account/CachedAccountDetails.java b/java/com/google/gerrit/server/account/CachedAccountDetails.java index 2ab6174..a8e409d 100644 --- a/java/com/google/gerrit/server/account/CachedAccountDetails.java +++ b/java/com/google/gerrit/server/account/CachedAccountDetails.java
@@ -32,6 +32,7 @@ import com.google.gerrit.server.config.CachedPreferences; import java.time.Instant; import java.util.Map; +import java.util.Optional; import org.eclipse.jgit.lib.ObjectId; /** Details of an account that are cached persistently in {@link AccountCache}. */ @@ -131,7 +132,11 @@ serialized.addProjectWatchProto(proto); } - serialized.setUserPreferences(cachedAccountDetails.preferences().config()); + Optional<Cache.CachedPreferencesProto> cachedPreferencesProto = + cachedAccountDetails.preferences().nonEmptyConfig(); + if (cachedPreferencesProto.isPresent()) { + serialized.setUserPreferences(cachedPreferencesProto.get()); + } return Protos.toByteArray(serialized.build()); } @@ -166,7 +171,7 @@ return CachedAccountDetails.create( account, projectWatches.build(), - CachedPreferences.fromString(proto.getUserPreferences())); + CachedPreferences.fromCachedPreferencesProto(proto.getUserPreferences())); } } }
diff --git a/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java b/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java index f0343b5..1da396e 100644 --- a/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java +++ b/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java
@@ -214,7 +214,7 @@ try (Timer0.Context ignored = readSingleLatency.start()) { cfg = new AccountConfig(accountId, allUsersName, allUsersRepository).load(); defaultPreferences = - CachedPreferences.fromConfig( + CachedPreferences.fromLegacyConfig( VersionedDefaultPreferences.get(allUsersRepository, allUsersName)); }
diff --git a/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java b/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java index cf57634..a12002f 100644 --- a/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java +++ b/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java
@@ -301,7 +301,7 @@ updateExternalIdNotes( repo, accountConfig.getExternalIdsRev(), accountId, accountDelta); CachedPreferences defaultPreferences = - CachedPreferences.fromConfig( + CachedPreferences.fromLegacyConfig( VersionedDefaultPreferences.get(repo, allUsersName)); return new UpdatedAccount(message, accountConfig, defaultPreferences, true); @@ -323,7 +323,7 @@ return repo -> { AccountConfig accountConfig = read(repo, updateArguments.accountId); CachedPreferences defaultPreferences = - CachedPreferences.fromConfig(VersionedDefaultPreferences.get(repo, allUsersName)); + CachedPreferences.fromLegacyConfig(VersionedDefaultPreferences.get(repo, allUsersName)); Optional<AccountState> accountState = AccountsNoteDbImpl.getFromAccountConfig(externalIds, accountConfig, defaultPreferences); if (!accountState.isPresent()) { @@ -343,7 +343,7 @@ accountConfig.setAccountDelta(delta); CachedPreferences cachedDefaultPreferences = - CachedPreferences.fromConfig(VersionedDefaultPreferences.get(repo, allUsersName)); + CachedPreferences.fromLegacyConfig(VersionedDefaultPreferences.get(repo, allUsersName)); return new UpdatedAccount( updateArguments.message, accountConfig, cachedDefaultPreferences, false); };
diff --git a/java/com/google/gerrit/server/config/CachedPreferences.java b/java/com/google/gerrit/server/config/CachedPreferences.java index 388f58a..169d9ec 100644 --- a/java/com/google/gerrit/server/config/CachedPreferences.java +++ b/java/com/google/gerrit/server/config/CachedPreferences.java
@@ -15,82 +15,154 @@ package com.google.gerrit.server.config; import com.google.auto.value.AutoValue; +import com.google.common.base.Function; import com.google.gerrit.common.Nullable; import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.extensions.client.DiffPreferencesInfo; import com.google.gerrit.extensions.client.EditPreferencesInfo; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; +import com.google.gerrit.proto.Entities.UserPreferences; +import com.google.gerrit.server.cache.proto.Cache.CachedPreferencesProto; import java.util.Optional; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; /** * Container class for preferences serialized as Git-style config files. Keeps the values as {@link - * String}s as they are immutable and thread-safe. + * CachedPreferencesProto}s as they are immutable and thread-safe. + * + * <p>The config string wrapped by this class might represent different structures. See {@link + * CachedPreferencesProto} for more details. */ @AutoValue public abstract class CachedPreferences { + public static final CachedPreferences EMPTY = + fromCachedPreferencesProto(CachedPreferencesProto.getDefaultInstance()); - public static CachedPreferences EMPTY = fromString(""); + protected abstract CachedPreferencesProto config(); - public abstract String config(); - - /** Returns a cache-able representation of the config. */ - public static CachedPreferences fromConfig(Config cfg) { - return new AutoValue_CachedPreferences(cfg.toText()); + public Optional<CachedPreferencesProto> nonEmptyConfig() { + return config().equals(EMPTY.config()) ? Optional.empty() : Optional.of(config()); + } + /** Returns a cache-able representation of the preferences proto. */ + public static CachedPreferences fromUserPreferencesProto(UserPreferences proto) { + return fromCachedPreferencesProto( + CachedPreferencesProto.newBuilder().setUserPreferences(proto).build()); } - /** - * Returns a cache-able representation of the config. To be used only when constructing a {@link - * CachedPreferences} from a serialized, cached value. - */ - public static CachedPreferences fromString(String cfg) { - return new AutoValue_CachedPreferences(cfg); + /** Returns a cache-able representation of the git config. */ + public static CachedPreferences fromLegacyConfig(Config cfg) { + return fromCachedPreferencesProto( + CachedPreferencesProto.newBuilder().setLegacyGitConfig(cfg.toText()).build()); + } + + /** Returns a cache-able representation of the preferences proto. */ + public static CachedPreferences fromCachedPreferencesProto( + @Nullable CachedPreferencesProto proto) { + if (proto != null) { + return new AutoValue_CachedPreferences(proto); + } + return EMPTY; } public static GeneralPreferencesInfo general( Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) { - try { - return PreferencesParserUtil.parseGeneralPreferences( - userPreferences.asConfig(), configOrNull(defaultPreferences), null); - } catch (ConfigInvalidException e) { - return GeneralPreferencesInfo.defaults(); - } - } - - public static EditPreferencesInfo edit( - Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) { - try { - return PreferencesParserUtil.parseEditPreferences( - userPreferences.asConfig(), configOrNull(defaultPreferences), null); - } catch (ConfigInvalidException e) { - return EditPreferencesInfo.defaults(); - } + return getPreferences( + defaultPreferences, + userPreferences, + PreferencesParserUtil::parseGeneralPreferences, + p -> + UserPreferencesConverter.GeneralPreferencesInfoConverter.fromProto( + p.getGeneralPreferencesInfo()), + GeneralPreferencesInfo.defaults()); } public static DiffPreferencesInfo diff( Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) { - try { - return PreferencesParserUtil.parseDiffPreferences( - userPreferences.asConfig(), configOrNull(defaultPreferences), null); - } catch (ConfigInvalidException e) { - return DiffPreferencesInfo.defaults(); - } + return getPreferences( + defaultPreferences, + userPreferences, + PreferencesParserUtil::parseDiffPreferences, + p -> + UserPreferencesConverter.DiffPreferencesInfoConverter.fromProto( + p.getDiffPreferencesInfo()), + DiffPreferencesInfo.defaults()); + } + + public static EditPreferencesInfo edit( + Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) { + return getPreferences( + defaultPreferences, + userPreferences, + PreferencesParserUtil::parseEditPreferences, + p -> + UserPreferencesConverter.EditPreferencesInfoConverter.fromProto( + p.getEditPreferencesInfo()), + EditPreferencesInfo.defaults()); } public Config asConfig() { - Config cfg = new Config(); try { - cfg.fromText(config()); + switch (config().getPreferencesCase()) { + case LEGACY_GIT_CONFIG: + // continue below + case PREFERENCES_NOT_SET: + Config cfg = new Config(); + cfg.fromText(config().getLegacyGitConfig()); + return cfg; + case USER_PREFERENCES: + break; + } } catch (ConfigInvalidException e) { - // Programmer error: We have parsed this config before and are unable to parse it now. throw new StorageException(e); } - return cfg; + throw new StorageException( + String.format( + "Cannot parse the given config as a CachedPreferencesProto proto. Got [%s]", config())); + } + + public UserPreferences asUserPreferencesProto() { + if (config().hasUserPreferences()) { + return config().getUserPreferences(); + } + throw new StorageException( + String.format( + "Cannot parse the given config as a UserPreferences proto. Got [%s]", config())); } @Nullable private static Config configOrNull(Optional<CachedPreferences> cachedPreferences) { return cachedPreferences.map(CachedPreferences::asConfig).orElse(null); } + + @FunctionalInterface + private interface ComputePreferencesFn<PreferencesT> { + PreferencesT apply(Config cfg, @Nullable Config defaultCfg, @Nullable PreferencesT input) + throws ConfigInvalidException; + } + + private static <PreferencesT> PreferencesT getPreferences( + Optional<CachedPreferences> defaultPreferences, + CachedPreferences userPreferences, + ComputePreferencesFn<PreferencesT> computePreferencesFn, + Function<UserPreferences, PreferencesT> fromUserPreferencesFn, + PreferencesT javaDefaults) { + try { + CachedPreferencesProto userPreferencesProto = userPreferences.config(); + switch (userPreferencesProto.getPreferencesCase()) { + case USER_PREFERENCES: + PreferencesT pref = + fromUserPreferencesFn.apply(userPreferencesProto.getUserPreferences()); + return computePreferencesFn.apply(new Config(), configOrNull(defaultPreferences), pref); + case LEGACY_GIT_CONFIG: + return computePreferencesFn.apply( + userPreferences.asConfig(), configOrNull(defaultPreferences), null); + case PREFERENCES_NOT_SET: + throw new ConfigInvalidException("Invalid config " + userPreferences); + } + } catch (ConfigInvalidException e) { + return javaDefaults; + } + return javaDefaults; + } }
diff --git a/java/com/google/gerrit/server/config/DefaultPreferencesCache.java b/java/com/google/gerrit/server/config/DefaultPreferencesCache.java index 39adb48..28b9507 100644 --- a/java/com/google/gerrit/server/config/DefaultPreferencesCache.java +++ b/java/com/google/gerrit/server/config/DefaultPreferencesCache.java
@@ -22,7 +22,7 @@ * Static member to be returned when there is no default config. This prevents re-instantiating * many {@link CachedPreferences} in this case. */ - CachedPreferences EMPTY = CachedPreferences.fromString(""); + CachedPreferences EMPTY = CachedPreferences.EMPTY; /** Returns a cached instance of {@link CachedPreferences}. */ CachedPreferences get();
diff --git a/java/com/google/gerrit/server/config/DefaultPreferencesCacheImpl.java b/java/com/google/gerrit/server/config/DefaultPreferencesCacheImpl.java index f8156a7..854a5c9 100644 --- a/java/com/google/gerrit/server/config/DefaultPreferencesCacheImpl.java +++ b/java/com/google/gerrit/server/config/DefaultPreferencesCacheImpl.java
@@ -99,7 +99,7 @@ try (Repository allUsersRepo = repositoryManager.openRepository(allUsersName)) { VersionedDefaultPreferences versionedDefaultPreferences = new VersionedDefaultPreferences(); versionedDefaultPreferences.load(allUsersName, allUsersRepo, key); - return CachedPreferences.fromConfig(versionedDefaultPreferences.getConfig()); + return CachedPreferences.fromLegacyConfig(versionedDefaultPreferences.getConfig()); } } }
diff --git a/java/com/google/gerrit/server/config/UserPreferencesConverter.java b/java/com/google/gerrit/server/config/UserPreferencesConverter.java new file mode 100644 index 0000000..bb611ad --- /dev/null +++ b/java/com/google/gerrit/server/config/UserPreferencesConverter.java
@@ -0,0 +1,343 @@ +// 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.config; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.gerrit.extensions.client.DiffPreferencesInfo; +import com.google.gerrit.extensions.client.EditPreferencesInfo; +import com.google.gerrit.extensions.client.GeneralPreferencesInfo; +import com.google.gerrit.extensions.client.MenuItem; +import com.google.gerrit.proto.Entities.UserPreferences; +import com.google.protobuf.Message; +import com.google.protobuf.ProtocolMessageEnum; +import java.util.function.Function; + +/** + * Converters for user preferences data classes + * + * <p>Upstream, we use java representations of the preference classes. Internally, we store proto + * equivalents in Spanner. + */ +final class UserPreferencesConverter { + static final class GeneralPreferencesInfoConverter { + public static UserPreferences.GeneralPreferencesInfo toProto(GeneralPreferencesInfo info) { + UserPreferences.GeneralPreferencesInfo.Builder builder = + UserPreferences.GeneralPreferencesInfo.newBuilder(); + builder = setIfNotNull(builder, builder::setChangesPerPage, info.changesPerPage); + builder = setIfNotNull(builder, builder::setDownloadScheme, info.downloadScheme); + builder = + setEnumIfNotNull( + builder, + builder::setTheme, + UserPreferences.GeneralPreferencesInfo.Theme::valueOf, + info.theme); + builder = + setEnumIfNotNull( + builder, + builder::setDateFormat, + UserPreferences.GeneralPreferencesInfo.DateFormat::valueOf, + info.dateFormat); + builder = + setEnumIfNotNull( + builder, + builder::setTimeFormat, + UserPreferences.GeneralPreferencesInfo.TimeFormat::valueOf, + info.timeFormat); + builder = setIfNotNull(builder, builder::setExpandInlineDiffs, info.expandInlineDiffs); + builder = + setIfNotNull( + builder, builder::setRelativeDateInChangeTable, info.relativeDateInChangeTable); + builder = + setEnumIfNotNull( + builder, + builder::setDiffView, + UserPreferences.GeneralPreferencesInfo.DiffView::valueOf, + info.diffView); + builder = setIfNotNull(builder, builder::setSizeBarInChangeTable, info.sizeBarInChangeTable); + builder = + setIfNotNull(builder, builder::setLegacycidInChangeTable, info.legacycidInChangeTable); + builder = + setIfNotNull(builder, builder::setMuteCommonPathPrefixes, info.muteCommonPathPrefixes); + builder = setIfNotNull(builder, builder::setSignedOffBy, info.signedOffBy); + builder = + setEnumIfNotNull( + builder, + builder::setEmailStrategy, + UserPreferences.GeneralPreferencesInfo.EmailStrategy::valueOf, + info.emailStrategy); + builder = + setEnumIfNotNull( + builder, + builder::setEmailFormat, + UserPreferences.GeneralPreferencesInfo.EmailFormat::valueOf, + info.emailFormat); + builder = + setEnumIfNotNull( + builder, + builder::setDefaultBaseForMerges, + UserPreferences.GeneralPreferencesInfo.DefaultBase::valueOf, + info.defaultBaseForMerges); + builder = + setIfNotNull(builder, builder::setPublishCommentsOnPush, info.publishCommentsOnPush); + builder = + setIfNotNull( + builder, builder::setDisableKeyboardShortcuts, info.disableKeyboardShortcuts); + builder = + setIfNotNull( + builder, builder::setDisableTokenHighlighting, info.disableTokenHighlighting); + builder = + setIfNotNull(builder, builder::setWorkInProgressByDefault, info.workInProgressByDefault); + if (info.my != null) { + builder = + builder.addAllMyMenuItems( + info.my.stream().map(i -> menuItemToProto(i)).collect(toImmutableList())); + } + if (info.changeTable != null) { + builder = builder.addAllChangeTable(info.changeTable); + } + builder = + setIfNotNull( + builder, builder::setAllowBrowserNotifications, info.allowBrowserNotifications); + return builder.build(); + } + + public static GeneralPreferencesInfo fromProto(UserPreferences.GeneralPreferencesInfo proto) { + GeneralPreferencesInfo res = new GeneralPreferencesInfo(); + res.changesPerPage = proto.hasChangesPerPage() ? proto.getChangesPerPage() : null; + res.downloadScheme = proto.hasDownloadScheme() ? proto.getDownloadScheme() : null; + res.theme = + proto.hasTheme() ? GeneralPreferencesInfo.Theme.valueOf(proto.getTheme().name()) : null; + res.dateFormat = + proto.hasDateFormat() + ? GeneralPreferencesInfo.DateFormat.valueOf(proto.getDateFormat().name()) + : null; + res.timeFormat = + proto.hasTimeFormat() + ? GeneralPreferencesInfo.TimeFormat.valueOf(proto.getTimeFormat().name()) + : null; + res.expandInlineDiffs = proto.hasExpandInlineDiffs() ? proto.getExpandInlineDiffs() : null; + res.relativeDateInChangeTable = + proto.hasRelativeDateInChangeTable() ? proto.getRelativeDateInChangeTable() : null; + res.diffView = + proto.hasDiffView() + ? GeneralPreferencesInfo.DiffView.valueOf(proto.getDiffView().name()) + : null; + res.sizeBarInChangeTable = + proto.hasSizeBarInChangeTable() ? proto.getSizeBarInChangeTable() : null; + res.legacycidInChangeTable = + proto.hasLegacycidInChangeTable() ? proto.getLegacycidInChangeTable() : null; + res.muteCommonPathPrefixes = + proto.hasMuteCommonPathPrefixes() ? proto.getMuteCommonPathPrefixes() : null; + res.signedOffBy = proto.hasSignedOffBy() ? proto.getSignedOffBy() : null; + res.emailStrategy = + proto.hasEmailStrategy() + ? GeneralPreferencesInfo.EmailStrategy.valueOf(proto.getEmailStrategy().name()) + : null; + res.emailFormat = + proto.hasEmailFormat() + ? GeneralPreferencesInfo.EmailFormat.valueOf(proto.getEmailFormat().name()) + : null; + res.defaultBaseForMerges = + proto.hasDefaultBaseForMerges() + ? GeneralPreferencesInfo.DefaultBase.valueOf(proto.getDefaultBaseForMerges().name()) + : null; + res.publishCommentsOnPush = + proto.hasPublishCommentsOnPush() ? proto.getPublishCommentsOnPush() : null; + res.disableKeyboardShortcuts = + proto.hasDisableKeyboardShortcuts() ? proto.getDisableKeyboardShortcuts() : null; + res.disableTokenHighlighting = + proto.hasDisableTokenHighlighting() ? proto.getDisableTokenHighlighting() : null; + res.workInProgressByDefault = + proto.hasWorkInProgressByDefault() ? proto.getWorkInProgressByDefault() : null; + res.my = + proto.getMyMenuItemsCount() != 0 + ? proto.getMyMenuItemsList().stream() + .map(p -> menuItemFromProto(p)) + .collect(toImmutableList()) + : null; + res.changeTable = proto.getChangeTableCount() != 0 ? proto.getChangeTableList() : null; + res.allowBrowserNotifications = + proto.hasAllowBrowserNotifications() ? proto.getAllowBrowserNotifications() : null; + return res; + } + + private static UserPreferences.GeneralPreferencesInfo.MenuItem menuItemToProto( + MenuItem javaItem) { + UserPreferences.GeneralPreferencesInfo.MenuItem.Builder builder = + UserPreferences.GeneralPreferencesInfo.MenuItem.newBuilder(); + builder = setIfNotNull(builder, builder::setName, javaItem.name); + builder = setIfNotNull(builder, builder::setUrl, javaItem.url); + builder = setIfNotNull(builder, builder::setTarget, javaItem.target); + builder = setIfNotNull(builder, builder::setId, javaItem.id); + return builder.build(); + } + + private static MenuItem menuItemFromProto( + UserPreferences.GeneralPreferencesInfo.MenuItem proto) { + return new MenuItem( + proto.hasName() ? proto.getName() : null, + proto.hasUrl() ? proto.getUrl() : null, + proto.hasTarget() ? proto.getTarget() : null, + proto.hasId() ? proto.getId() : null); + } + + private GeneralPreferencesInfoConverter() {} + } + + static final class DiffPreferencesInfoConverter { + public static UserPreferences.DiffPreferencesInfo toProto(DiffPreferencesInfo info) { + UserPreferences.DiffPreferencesInfo.Builder builder = + UserPreferences.DiffPreferencesInfo.newBuilder(); + builder = setIfNotNull(builder, builder::setContext, info.context); + builder = setIfNotNull(builder, builder::setTabSize, info.tabSize); + builder = setIfNotNull(builder, builder::setFontSize, info.fontSize); + builder = setIfNotNull(builder, builder::setLineLength, info.lineLength); + builder = setIfNotNull(builder, builder::setCursorBlinkRate, info.cursorBlinkRate); + builder = setIfNotNull(builder, builder::setExpandAllComments, info.expandAllComments); + builder = setIfNotNull(builder, builder::setIntralineDifference, info.intralineDifference); + builder = setIfNotNull(builder, builder::setManualReview, info.manualReview); + builder = setIfNotNull(builder, builder::setShowLineEndings, info.showLineEndings); + builder = setIfNotNull(builder, builder::setShowTabs, info.showTabs); + builder = setIfNotNull(builder, builder::setShowWhitespaceErrors, info.showWhitespaceErrors); + builder = setIfNotNull(builder, builder::setSyntaxHighlighting, info.syntaxHighlighting); + builder = setIfNotNull(builder, builder::setHideTopMenu, info.hideTopMenu); + builder = + setIfNotNull(builder, builder::setAutoHideDiffTableHeader, info.autoHideDiffTableHeader); + builder = setIfNotNull(builder, builder::setHideLineNumbers, info.hideLineNumbers); + builder = setIfNotNull(builder, builder::setRenderEntireFile, info.renderEntireFile); + builder = setIfNotNull(builder, builder::setHideEmptyPane, info.hideEmptyPane); + builder = setIfNotNull(builder, builder::setMatchBrackets, info.matchBrackets); + builder = setIfNotNull(builder, builder::setLineWrapping, info.lineWrapping); + builder = + setEnumIfNotNull( + builder, + builder::setIgnoreWhitespace, + UserPreferences.DiffPreferencesInfo.Whitespace::valueOf, + info.ignoreWhitespace); + builder = setIfNotNull(builder, builder::setRetainHeader, info.retainHeader); + builder = setIfNotNull(builder, builder::setSkipDeleted, info.skipDeleted); + builder = setIfNotNull(builder, builder::setSkipUnchanged, info.skipUnchanged); + builder = setIfNotNull(builder, builder::setSkipUncommented, info.skipUncommented); + return builder.build(); + } + + public static DiffPreferencesInfo fromProto(UserPreferences.DiffPreferencesInfo proto) { + DiffPreferencesInfo res = new DiffPreferencesInfo(); + res.context = proto.hasContext() ? proto.getContext() : null; + res.tabSize = proto.hasTabSize() ? proto.getTabSize() : null; + res.fontSize = proto.hasFontSize() ? proto.getFontSize() : null; + res.lineLength = proto.hasLineLength() ? proto.getLineLength() : null; + res.cursorBlinkRate = proto.hasCursorBlinkRate() ? proto.getCursorBlinkRate() : null; + res.expandAllComments = proto.hasExpandAllComments() ? proto.getExpandAllComments() : null; + res.intralineDifference = + proto.hasIntralineDifference() ? proto.getIntralineDifference() : null; + res.manualReview = proto.hasManualReview() ? proto.getManualReview() : null; + res.showLineEndings = proto.hasShowLineEndings() ? proto.getShowLineEndings() : null; + res.showTabs = proto.hasShowTabs() ? proto.getShowTabs() : null; + res.showWhitespaceErrors = + proto.hasShowWhitespaceErrors() ? proto.getShowWhitespaceErrors() : null; + res.syntaxHighlighting = proto.hasSyntaxHighlighting() ? proto.getSyntaxHighlighting() : null; + res.hideTopMenu = proto.hasHideTopMenu() ? proto.getHideTopMenu() : null; + res.autoHideDiffTableHeader = + proto.hasAutoHideDiffTableHeader() ? proto.getAutoHideDiffTableHeader() : null; + res.hideLineNumbers = proto.hasHideLineNumbers() ? proto.getHideLineNumbers() : null; + res.renderEntireFile = proto.hasRenderEntireFile() ? proto.getRenderEntireFile() : null; + res.hideEmptyPane = proto.hasHideEmptyPane() ? proto.getHideEmptyPane() : null; + res.matchBrackets = proto.hasMatchBrackets() ? proto.getMatchBrackets() : null; + res.lineWrapping = proto.hasLineWrapping() ? proto.getLineWrapping() : null; + res.ignoreWhitespace = + proto.hasIgnoreWhitespace() + ? DiffPreferencesInfo.Whitespace.valueOf(proto.getIgnoreWhitespace().name()) + : null; + res.retainHeader = proto.hasRetainHeader() ? proto.getRetainHeader() : null; + res.skipDeleted = proto.hasSkipDeleted() ? proto.getSkipDeleted() : null; + res.skipUnchanged = proto.hasSkipUnchanged() ? proto.getSkipUnchanged() : null; + res.skipUncommented = proto.hasSkipUncommented() ? proto.getSkipUncommented() : null; + return res; + } + + private DiffPreferencesInfoConverter() {} + } + + static final class EditPreferencesInfoConverter { + public static UserPreferences.EditPreferencesInfo toProto(EditPreferencesInfo info) { + UserPreferences.EditPreferencesInfo.Builder builder = + UserPreferences.EditPreferencesInfo.newBuilder(); + builder = setIfNotNull(builder, builder::setTabSize, info.tabSize); + builder = setIfNotNull(builder, builder::setLineLength, info.lineLength); + builder = setIfNotNull(builder, builder::setIndentUnit, info.indentUnit); + builder = setIfNotNull(builder, builder::setCursorBlinkRate, info.cursorBlinkRate); + builder = setIfNotNull(builder, builder::setHideTopMenu, info.hideTopMenu); + builder = setIfNotNull(builder, builder::setShowTabs, info.showTabs); + builder = setIfNotNull(builder, builder::setShowWhitespaceErrors, info.showWhitespaceErrors); + builder = setIfNotNull(builder, builder::setSyntaxHighlighting, info.syntaxHighlighting); + builder = setIfNotNull(builder, builder::setHideLineNumbers, info.hideLineNumbers); + builder = setIfNotNull(builder, builder::setMatchBrackets, info.matchBrackets); + builder = setIfNotNull(builder, builder::setLineWrapping, info.lineWrapping); + builder = setIfNotNull(builder, builder::setIndentWithTabs, info.indentWithTabs); + builder = setIfNotNull(builder, builder::setAutoCloseBrackets, info.autoCloseBrackets); + builder = setIfNotNull(builder, builder::setShowBase, info.showBase); + return builder.build(); + } + + public static EditPreferencesInfo fromProto(UserPreferences.EditPreferencesInfo proto) { + EditPreferencesInfo res = new EditPreferencesInfo(); + res.tabSize = proto.hasTabSize() ? proto.getTabSize() : null; + res.lineLength = proto.hasLineLength() ? proto.getLineLength() : null; + res.indentUnit = proto.hasIndentUnit() ? proto.getIndentUnit() : null; + res.cursorBlinkRate = proto.hasCursorBlinkRate() ? proto.getCursorBlinkRate() : null; + res.hideTopMenu = proto.hasHideTopMenu() ? proto.getHideTopMenu() : null; + res.showTabs = proto.hasShowTabs() ? proto.getShowTabs() : null; + res.showWhitespaceErrors = + proto.hasShowWhitespaceErrors() ? proto.getShowWhitespaceErrors() : null; + res.syntaxHighlighting = proto.hasSyntaxHighlighting() ? proto.getSyntaxHighlighting() : null; + res.hideLineNumbers = proto.hasHideLineNumbers() ? proto.getHideLineNumbers() : null; + res.matchBrackets = proto.hasMatchBrackets() ? proto.getMatchBrackets() : null; + res.lineWrapping = proto.hasLineWrapping() ? proto.getLineWrapping() : null; + res.indentWithTabs = proto.hasIndentWithTabs() ? proto.getIndentWithTabs() : null; + res.autoCloseBrackets = proto.hasAutoCloseBrackets() ? proto.getAutoCloseBrackets() : null; + res.showBase = proto.hasShowBase() ? proto.getShowBase() : null; + return res; + } + + private EditPreferencesInfoConverter() {} + } + + private static <ValueT, BuilderT extends Message.Builder> BuilderT setIfNotNull( + BuilderT builder, Function<ValueT, BuilderT> protoFieldSetterFn, ValueT javaField) { + if (javaField != null) { + return protoFieldSetterFn.apply(javaField); + } + return builder; + } + + private static < + JavaEnumT extends Enum<?>, + ProtoEnumT extends ProtocolMessageEnum, + BuilderT extends Message.Builder> + BuilderT setEnumIfNotNull( + BuilderT builder, + Function<ProtoEnumT, BuilderT> protoFieldSetterFn, + Function<String, ProtoEnumT> protoEnumFromNameFn, + JavaEnumT javaEnum) { + if (javaEnum != null) { + return protoFieldSetterFn.apply(protoEnumFromNameFn.apply(javaEnum.name())); + } + return builder; + } + + private UserPreferencesConverter() {} +}
diff --git a/javatests/com/google/gerrit/server/account/AccountCacheTest.java b/javatests/com/google/gerrit/server/account/AccountCacheTest.java index c1eff15..6628362 100644 --- a/javatests/com/google/gerrit/server/account/AccountCacheTest.java +++ b/javatests/com/google/gerrit/server/account/AccountCacheTest.java
@@ -21,9 +21,11 @@ import com.google.gerrit.entities.Account; import com.google.gerrit.entities.NotifyConfig; import com.google.gerrit.entities.Project; +import com.google.gerrit.proto.Entities; import com.google.gerrit.server.cache.proto.Cache; import com.google.gerrit.server.config.CachedPreferences; import java.time.Instant; +import org.eclipse.jgit.lib.Config; import org.junit.Test; /** @@ -49,7 +51,7 @@ .setPreferredEmail("foo@bar.tld") .build(); CachedAccountDetails original = - CachedAccountDetails.create(account, ImmutableMap.of(), CachedPreferences.fromString("")); + CachedAccountDetails.create(account, ImmutableMap.of(), CachedPreferences.EMPTY); byte[] serialized = SERIALIZER.serialize(original); Cache.AccountDetailsProto expected = Cache.AccountDetailsProto.newBuilder() @@ -71,7 +73,7 @@ @Test public void account_roundTripNullFields() throws Exception { CachedAccountDetails original = - CachedAccountDetails.create(ACCOUNT, ImmutableMap.of(), CachedPreferences.fromString("")); + CachedAccountDetails.create(ACCOUNT, ImmutableMap.of(), CachedPreferences.EMPTY); byte[] serialized = SERIALIZER.serialize(original); Cache.AccountDetailsProto expected = Cache.AccountDetailsProto.newBuilder().setAccount(ACCOUNT_PROTO).build(); @@ -80,16 +82,40 @@ } @Test - public void config_roundTrip() throws Exception { + public void config_gitConfig_roundTrip() throws Exception { + Config cfg = new Config(); + cfg.fromText("[general]\n\tfoo = bar"); CachedAccountDetails original = CachedAccountDetails.create( - ACCOUNT, ImmutableMap.of(), CachedPreferences.fromString("[general]\n\tfoo = bar")); + ACCOUNT, ImmutableMap.of(), CachedPreferences.fromLegacyConfig(cfg)); byte[] serialized = SERIALIZER.serialize(original); Cache.AccountDetailsProto expected = Cache.AccountDetailsProto.newBuilder() .setAccount(ACCOUNT_PROTO) - .setUserPreferences("[general]\n\tfoo = bar") + .setUserPreferences( + Cache.CachedPreferencesProto.newBuilder().setLegacyGitConfig(cfg.toText())) + .build(); + ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected); + Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original); + } + + @Test + public void config_protoConfig_roundTrip() throws Exception { + Entities.UserPreferences proto = + Entities.UserPreferences.newBuilder() + .setGeneralPreferencesInfo( + Entities.UserPreferences.GeneralPreferencesInfo.newBuilder().setChangesPerPage(17)) + .build(); + CachedAccountDetails original = + CachedAccountDetails.create( + ACCOUNT, ImmutableMap.of(), CachedPreferences.fromUserPreferencesProto(proto)); + + byte[] serialized = SERIALIZER.serialize(original); + Cache.AccountDetailsProto expected = + Cache.AccountDetailsProto.newBuilder() + .setAccount(ACCOUNT_PROTO) + .setUserPreferences(Cache.CachedPreferencesProto.newBuilder().setUserPreferences(proto)) .build(); ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected); Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original); @@ -103,7 +129,7 @@ CachedAccountDetails.create( ACCOUNT, ImmutableMap.of(key, ImmutableSet.of(NotifyConfig.NotifyType.ALL_COMMENTS)), - CachedPreferences.fromString("")); + CachedPreferences.EMPTY); byte[] serialized = SERIALIZER.serialize(original); Cache.AccountDetailsProto expected = @@ -127,7 +153,7 @@ CachedAccountDetails.create( ACCOUNT, ImmutableMap.of(key, ImmutableSet.of(NotifyConfig.NotifyType.ALL_COMMENTS)), - CachedPreferences.fromString("")); + CachedPreferences.EMPTY); byte[] serialized = SERIALIZER.serialize(original); Cache.AccountDetailsProto expected =
diff --git a/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java b/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java new file mode 100644 index 0000000..772f4b8 --- /dev/null +++ b/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java
@@ -0,0 +1,161 @@ +// 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.config; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static com.google.gerrit.testing.GerritJUnit.assertThrows; + +import com.google.gerrit.exceptions.StorageException; +import com.google.gerrit.extensions.client.DiffPreferencesInfo; +import com.google.gerrit.extensions.client.EditPreferencesInfo; +import com.google.gerrit.extensions.client.GeneralPreferencesInfo; +import com.google.gerrit.proto.Entities.UserPreferences; +import java.util.Optional; +import org.eclipse.jgit.lib.Config; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CachedPreferencesTest { + @Test + public void gitConfig_roundTrip() throws Exception { + Config originalCfg = new Config(); + originalCfg.fromText("[general]\n\tfoo = bar"); + + CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg); + Config res = pref.asConfig(); + + assertThat(res.toText()).isEqualTo(originalCfg.toText()); + } + + @Test + public void gitConfig_getGeneralPreferences() throws Exception { + Config originalCfg = new Config(); + originalCfg.fromText("[general]\n\tchangesPerPage = 2"); + + CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg); + GeneralPreferencesInfo general = CachedPreferences.general(Optional.empty(), pref); + + assertThat(general.changesPerPage).isEqualTo(2); + } + + @Test + public void gitConfig_getDiffPreferences() throws Exception { + Config originalCfg = new Config(); + originalCfg.fromText("[diff]\n\tcontext = 3"); + + CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg); + DiffPreferencesInfo diff = CachedPreferences.diff(Optional.empty(), pref); + + assertThat(diff.context).isEqualTo(3); + } + + @Test + public void gitConfig_getEditPreferences() throws Exception { + Config originalCfg = new Config(); + originalCfg.fromText("[edit]\n\ttabSize = 5"); + + CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg); + EditPreferencesInfo edit = CachedPreferences.edit(Optional.empty(), pref); + + assertThat(edit.tabSize).isEqualTo(5); + } + + @Test + public void userPreferencesProto_roundTrip() throws Exception { + UserPreferences originalProto = + UserPreferences.newBuilder() + .setGeneralPreferencesInfo( + UserPreferences.GeneralPreferencesInfo.newBuilder().setChangesPerPage(7)) + .build(); + + CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto); + UserPreferences res = pref.asUserPreferencesProto(); + + assertThat(res).isEqualTo(originalProto); + } + + @Test + public void userPreferencesProto_getGeneralPreferences() throws Exception { + UserPreferences originalProto = + UserPreferences.newBuilder() + .setGeneralPreferencesInfo( + UserPreferences.GeneralPreferencesInfo.newBuilder().setChangesPerPage(11)) + .build(); + + CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto); + GeneralPreferencesInfo general = CachedPreferences.general(Optional.empty(), pref); + + assertThat(general.changesPerPage).isEqualTo(11); + } + + @Test + public void userPreferencesProto_getDiffPreferences() throws Exception { + UserPreferences originalProto = + UserPreferences.newBuilder() + .setDiffPreferencesInfo(UserPreferences.DiffPreferencesInfo.newBuilder().setContext(13)) + .build(); + + CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto); + DiffPreferencesInfo diff = CachedPreferences.diff(Optional.empty(), pref); + + assertThat(diff.context).isEqualTo(13); + } + + @Test + public void userPreferencesProto_getEditPreferences() throws Exception { + UserPreferences originalProto = + UserPreferences.newBuilder() + .setEditPreferencesInfo(UserPreferences.EditPreferencesInfo.newBuilder().setTabSize(17)) + .build(); + + CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto); + EditPreferencesInfo edit = CachedPreferences.edit(Optional.empty(), pref); + + assertThat(edit.tabSize).isEqualTo(17); + } + + @Test + public void defaultPreferences_acceptingGitConfig() throws Exception { + Config cfg = new Config(); + cfg.fromText("[general]\n\tchangesPerPage = 19"); + CachedPreferences defaults = CachedPreferences.fromLegacyConfig(cfg); + CachedPreferences userPreferences = + CachedPreferences.fromUserPreferencesProto(UserPreferences.getDefaultInstance()); + + assertThat(CachedPreferences.general(Optional.of(defaults), userPreferences)).isNotNull(); + assertThat(CachedPreferences.diff(Optional.of(defaults), userPreferences)).isNotNull(); + assertThat(CachedPreferences.edit(Optional.of(defaults), userPreferences)).isNotNull(); + } + + @Test + public void defaultPreferences_throwingForProto() throws Exception { + CachedPreferences defaults = + CachedPreferences.fromUserPreferencesProto(UserPreferences.getDefaultInstance()); + CachedPreferences userPreferences = + CachedPreferences.fromUserPreferencesProto(UserPreferences.getDefaultInstance()); + assertThrows( + StorageException.class, + () -> CachedPreferences.general(Optional.of(defaults), userPreferences)); + assertThrows( + StorageException.class, + () -> CachedPreferences.diff(Optional.of(defaults), userPreferences)); + assertThrows( + StorageException.class, + () -> CachedPreferences.edit(Optional.of(defaults), userPreferences)); + } +}
diff --git a/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java b/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java new file mode 100644 index 0000000..bbc3c0a --- /dev/null +++ b/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java
@@ -0,0 +1,304 @@ +// 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.config; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static java.util.Arrays.stream; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.gerrit.extensions.client.DiffPreferencesInfo; +import com.google.gerrit.extensions.client.EditPreferencesInfo; +import com.google.gerrit.extensions.client.GeneralPreferencesInfo; +import com.google.gerrit.proto.Entities.UserPreferences; +import com.google.gerrit.proto.Entities.UserPreferences.DiffPreferencesInfo.Whitespace; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.DateFormat; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.DefaultBase; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.DiffView; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.EmailFormat; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.EmailStrategy; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.MenuItem; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.Theme; +import com.google.gerrit.proto.Entities.UserPreferences.GeneralPreferencesInfo.TimeFormat; +import com.google.gerrit.server.config.UserPreferencesConverter.DiffPreferencesInfoConverter; +import com.google.gerrit.server.config.UserPreferencesConverter.EditPreferencesInfoConverter; +import com.google.gerrit.server.config.UserPreferencesConverter.GeneralPreferencesInfoConverter; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import java.util.EnumSet; +import java.util.function.Function; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UserPreferencesConverterTest { + @Test + public void generalPreferencesInfo_compareEnumNames() { + // The converter assumes that the enum type equivalents have exactly the same values in both + // classes. This test goes over all the enums to verify this assumption. + // + // If this test breaks, you are likely changing an enum. Please add it to the upstream Java + // class first, and on import - also update the proto version and the converter. + ImmutableMap<String, EnumDescriptor> protoEnums = + getProtoEnum(UserPreferences.GeneralPreferencesInfo.getDescriptor()); + ImmutableMap<String, EnumSet<?>> javaEnums = getJavaEnums(GeneralPreferencesInfo.class); + assertThat(protoEnums.keySet()).containsExactlyElementsIn(javaEnums.keySet()); + for (String enumName : protoEnums.keySet()) { + ImmutableList<String> protoEnumValues = + protoEnums.get(enumName).getValues().stream() + .map(v -> v.getName()) + .collect(toImmutableList()); + ImmutableList<String> javaEnumValues = + javaEnums.get(enumName).stream().map(Enum::name).collect(toImmutableList()); + assertThat(protoEnumValues).containsExactlyElementsIn(javaEnumValues); + } + } + + @Test + public void generalPreferencesInfo_doubleConversionWithAllFieldsSet() { + UserPreferences.GeneralPreferencesInfo originalProto = + UserPreferences.GeneralPreferencesInfo.newBuilder() + .setChangesPerPage(42) + .setDownloadScheme("DownloadScheme") + .setTheme(Theme.DARK) + .setDateFormat(DateFormat.UK) + .setTimeFormat(TimeFormat.HHMM_24) + .setExpandInlineDiffs(true) + .setRelativeDateInChangeTable(true) + .setDiffView(DiffView.UNIFIED_DIFF) + .setSizeBarInChangeTable(true) + .setLegacycidInChangeTable(true) + .setMuteCommonPathPrefixes(true) + .setSignedOffBy(true) + .setEmailStrategy(EmailStrategy.CC_ON_OWN_COMMENTS) + .setEmailFormat(EmailFormat.HTML_PLAINTEXT) + .setDefaultBaseForMerges(DefaultBase.FIRST_PARENT) + .setPublishCommentsOnPush(true) + .setDisableKeyboardShortcuts(true) + .setDisableTokenHighlighting(true) + .setWorkInProgressByDefault(true) + .addAllMyMenuItems( + ImmutableList.of( + MenuItem.newBuilder() + .setUrl("url1") + .setName("name1") + .setTarget("target1") + .setId("id1") + .build(), + MenuItem.newBuilder() + .setUrl("url2") + .setName("name2") + .setTarget("target2") + .setId("id2") + .build())) + .addAllChangeTable(ImmutableList.of("table1", "table2")) + .setAllowBrowserNotifications(true) + .build(); + UserPreferences.GeneralPreferencesInfo resProto = + GeneralPreferencesInfoConverter.toProto( + GeneralPreferencesInfoConverter.fromProto(originalProto)); + assertThat(resProto).isEqualTo(originalProto); + } + + @Test + public void generalPreferencesInfo_emptyJavaToProto() { + GeneralPreferencesInfo info = new GeneralPreferencesInfo(); + UserPreferences.GeneralPreferencesInfo res = GeneralPreferencesInfoConverter.toProto(info); + assertThat(res).isEqualToDefaultInstance(); + } + + @Test + public void generalPreferencesInfo_defaultJavaToProto() { + GeneralPreferencesInfo info = GeneralPreferencesInfo.defaults(); + UserPreferences.GeneralPreferencesInfo res = GeneralPreferencesInfoConverter.toProto(info); + assertThat(res) + .ignoringFieldAbsence() + .isEqualTo(UserPreferences.GeneralPreferencesInfo.getDefaultInstance()); + } + + @Test + public void generalPreferencesInfo_emptyProtoToJava() { + UserPreferences.GeneralPreferencesInfo proto = + UserPreferences.GeneralPreferencesInfo.getDefaultInstance(); + GeneralPreferencesInfo res = GeneralPreferencesInfoConverter.fromProto(proto); + assertThat(res).isEqualTo(new GeneralPreferencesInfo()); + } + + @Test + public void diffPreferencesInfo_compareEnumNames() { + // The converter assumes that the enum type equivalents have exactly the same values in both + // classes. This test goes over all the enums to verify this assumption. + // + // If this test breaks, you are likely changing an enum. Please add it to the upstream Java + // class first, and on import - also update the proto version and the converter. + ImmutableMap<String, EnumDescriptor> protoEnums = + getProtoEnum(UserPreferences.DiffPreferencesInfo.getDescriptor()); + ImmutableMap<String, EnumSet<?>> javaEnums = getJavaEnums(DiffPreferencesInfo.class); + assertThat(protoEnums.keySet()).containsExactlyElementsIn(javaEnums.keySet()); + for (String enumName : protoEnums.keySet()) { + ImmutableList<String> protoEnumValues = + protoEnums.get(enumName).getValues().stream() + .map(v -> v.getName()) + .collect(toImmutableList()); + ImmutableList<String> javaEnumValues = + javaEnums.get(enumName).stream().map(Enum::name).collect(toImmutableList()); + assertThat(protoEnumValues).containsExactlyElementsIn(javaEnumValues); + } + } + + @Test + public void diffPreferencesInfo_doubleConversionWithAllFieldsSet() { + UserPreferences.DiffPreferencesInfo originalProto = + UserPreferences.DiffPreferencesInfo.newBuilder() + .setContext(1) + .setTabSize(2) + .setFontSize(3) + .setLineLength(4) + .setCursorBlinkRate(5) + .setExpandAllComments(false) + .setIntralineDifference(true) + .setManualReview(false) + .setShowLineEndings(true) + .setShowTabs(false) + .setShowWhitespaceErrors(true) + .setSyntaxHighlighting(false) + .setHideTopMenu(true) + .setAutoHideDiffTableHeader(false) + .setHideLineNumbers(true) + .setRenderEntireFile(false) + .setHideEmptyPane(true) + .setMatchBrackets(false) + .setLineWrapping(true) + .setIgnoreWhitespace(Whitespace.IGNORE_TRAILING) + .setRetainHeader(true) + .setSkipDeleted(false) + .setSkipUnchanged(true) + .setSkipUncommented(false) + .build(); + UserPreferences.DiffPreferencesInfo resProto = + DiffPreferencesInfoConverter.toProto(DiffPreferencesInfoConverter.fromProto(originalProto)); + assertThat(resProto).isEqualTo(originalProto); + } + + @Test + public void diffPreferencesInfo_emptyJavaToProto() { + DiffPreferencesInfo info = new DiffPreferencesInfo(); + UserPreferences.DiffPreferencesInfo res = DiffPreferencesInfoConverter.toProto(info); + assertThat(res).isEqualToDefaultInstance(); + } + + @Test + public void diffPreferencesInfo_defaultJavaToProto() { + DiffPreferencesInfo info = DiffPreferencesInfo.defaults(); + UserPreferences.DiffPreferencesInfo res = DiffPreferencesInfoConverter.toProto(info); + assertThat(res) + .ignoringFieldAbsence() + .isEqualTo(UserPreferences.DiffPreferencesInfo.getDefaultInstance()); + } + + @Test + public void diffPreferencesInfo_emptyProtoToJava() { + UserPreferences.DiffPreferencesInfo proto = + UserPreferences.DiffPreferencesInfo.getDefaultInstance(); + DiffPreferencesInfo res = DiffPreferencesInfoConverter.fromProto(proto); + assertThat(res).isEqualTo(new DiffPreferencesInfo()); + } + + @Test + public void editPreferencesInfo_compareEnumNames() { + // The converter assumes that the enum type equivalents have exactly the same values in both + // classes. This test goes over all the enums to verify this assumption. + // + // If this test breaks, you are likely changing an enum. Please add it to the upstream Java + // class first, and on import - also update the proto version and the converter. + ImmutableMap<String, EnumDescriptor> protoEnums = + getProtoEnum(UserPreferences.EditPreferencesInfo.getDescriptor()); + ImmutableMap<String, EnumSet<?>> javaEnums = getJavaEnums(EditPreferencesInfo.class); + assertThat(protoEnums.keySet()).containsExactlyElementsIn(javaEnums.keySet()); + for (String enumName : protoEnums.keySet()) { + ImmutableList<String> protoEnumValues = + protoEnums.get(enumName).getValues().stream() + .map(v -> v.getName()) + .collect(toImmutableList()); + ImmutableList<String> javaEnumValues = + javaEnums.get(enumName).stream().map(Enum::name).collect(toImmutableList()); + assertThat(protoEnumValues).containsExactlyElementsIn(javaEnumValues); + } + } + + @Test + public void editPreferencesInfo_doubleConversionWithAllFieldsSet() { + UserPreferences.EditPreferencesInfo originalProto = + UserPreferences.EditPreferencesInfo.newBuilder() + .setTabSize(2) + .setLineLength(3) + .setIndentUnit(5) + .setCursorBlinkRate(7) + .setHideTopMenu(true) + .setShowTabs(false) + .setShowWhitespaceErrors(true) + .setSyntaxHighlighting(false) + .setHideLineNumbers(true) + .setMatchBrackets(false) + .setLineWrapping(true) + .setIndentWithTabs(false) + .setAutoCloseBrackets(true) + .setShowBase(false) + .build(); + UserPreferences.EditPreferencesInfo resProto = + EditPreferencesInfoConverter.toProto(EditPreferencesInfoConverter.fromProto(originalProto)); + assertThat(resProto).isEqualTo(originalProto); + } + + @Test + public void editPreferencesInfo_emptyJavaToProto() { + EditPreferencesInfo info = new EditPreferencesInfo(); + UserPreferences.EditPreferencesInfo res = EditPreferencesInfoConverter.toProto(info); + assertThat(res).isEqualToDefaultInstance(); + } + + @Test + public void editPreferencesInfo_defaultJavaToProto() { + EditPreferencesInfo info = EditPreferencesInfo.defaults(); + UserPreferences.EditPreferencesInfo res = EditPreferencesInfoConverter.toProto(info); + assertThat(res) + .ignoringFieldAbsence() + .isEqualTo(UserPreferences.EditPreferencesInfo.getDefaultInstance()); + } + + @Test + public void editPreferencesInfo_emptyProtoToJava() { + UserPreferences.EditPreferencesInfo proto = + UserPreferences.EditPreferencesInfo.getDefaultInstance(); + EditPreferencesInfo res = EditPreferencesInfoConverter.fromProto(proto); + assertThat(res).isEqualTo(new EditPreferencesInfo()); + } + + private ImmutableMap<String, EnumDescriptor> getProtoEnum(Descriptor d) { + return d.getEnumTypes().stream().collect(toImmutableMap(e -> e.getName(), Function.identity())); + } + + @SuppressWarnings("unchecked") + private ImmutableMap<String, EnumSet<?>> getJavaEnums(Class<?> c) { + return stream(c.getDeclaredClasses()) + .filter(Class::isEnum) + .collect( + toImmutableMap(Class::getSimpleName, e -> EnumSet.allOf(e.asSubclass(Enum.class)))); + } +}
diff --git a/proto/cache.proto b/proto/cache.proto index 7e38d92..87ae0e4 100644 --- a/proto/cache.proto +++ b/proto/cache.proto
@@ -323,6 +323,15 @@ repeated string notify_type = 3; } +// Serialized user preferences. +// Next ID: 3 +message CachedPreferencesProto { + oneof Preferences { + devtools.gerritcodereview.UserPreferences user_preferences = 1; + string legacy_git_config = 2; + } +} + // Serialized form of // com.google.gerrit.entities.Account. // Next ID: 9 @@ -345,11 +354,12 @@ } // Serialized form of com.google.gerrit.server.account.CachedAccountDetails. -// Next ID: 4 +// Next ID: 5 message AccountDetailsProto { AccountProto account = 1; repeated ProjectWatchProto project_watch_proto = 2; - string user_preferences = 3; + CachedPreferencesProto user_preferences = 4; + reserved 3; } // Serialized form of com.google.gerrit.entities.Project.
diff --git a/proto/entities.proto b/proto/entities.proto index 20d5444..372426a 100644 --- a/proto/entities.proto +++ b/proto/entities.proto
@@ -168,3 +168,145 @@ message PaginationToken { optional string next_page_token = 1; } + +// Proto representation of the User preferences classes +// Next ID: 4 +message UserPreferences { + // Next ID: 23 + message GeneralPreferencesInfo { + // Number of changes to show in a screen. + optional int32 changes_per_page = 1 [default = 25]; + + // Type of download URL the user prefers to use. */ + optional string download_scheme = 2; + + enum Theme { + AUTO = 0; + DARK = 1; + LIGHT = 2; + } + optional Theme theme = 3; + + enum DateFormat { + STD = 0; + US = 1; + ISO = 2; + EURO = 3; + UK = 4; + } + optional DateFormat date_format = 4; + + enum TimeFormat { + HHMM_12 = 0; + HHMM_24 = 1; + } + optional TimeFormat time_format = 5; + + optional bool expand_inline_diffs = 6; + optional bool relative_date_in_change_table = 20; + + enum DiffView { + SIDE_BY_SIDE = 0; + UNIFIED_DIFF = 1; + } + optional DiffView diff_view = 21; + + optional bool size_bar_in_change_table = 22 [default = true]; + optional bool legacycid_in_change_table = 7; + optional bool mute_common_path_prefixes = 8 [default = true]; + optional bool signed_off_by = 9; + + enum EmailStrategy { + ENABLED = 0; + CC_ON_OWN_COMMENTS = 1; + ATTENTION_SET_ONLY = 2; + DISABLED = 3; + } + optional EmailStrategy email_strategy = 10; + + enum EmailFormat { + PLAINTEXT = 0; + HTML_PLAINTEXT = 1; + } + optional EmailFormat email_format = 11 [default = HTML_PLAINTEXT]; + + enum DefaultBase { + AUTO_MERGE = 0; + FIRST_PARENT = 1; + } + optional DefaultBase default_base_for_merges = 12 [default = FIRST_PARENT]; + + optional bool publish_comments_on_push = 13; + optional bool disable_keyboard_shortcuts = 14; + optional bool disable_token_highlighting = 15; + optional bool work_in_progress_by_default = 16; + + message MenuItem { + optional string url = 1; + optional string name = 2; + optional string target = 3; + optional string id = 4; + } + repeated MenuItem my_menu_items = 17; + + repeated string change_table = 18; + optional bool allow_browser_notifications = 19 [default = true]; + } + optional GeneralPreferencesInfo general_preferences_info = 1; + + // Next ID: 25 + message DiffPreferencesInfo { + optional int32 context = 1 [default = 10]; + optional int32 tab_size = 2 [default = 8]; + optional int32 font_size = 3 [default = 12]; + optional int32 line_length = 4 [default = 100]; + optional int32 cursor_blink_rate = 5; + optional bool expand_all_comments = 6; + optional bool intraline_difference = 7 [default = true]; + optional bool manual_review = 8; + optional bool show_line_endings = 9 [default = true]; + optional bool show_tabs = 10 [default = true]; + optional bool show_whitespace_errors = 11 [default = true]; + optional bool syntax_highlighting = 12 [default = true]; + optional bool hide_top_menu = 13; + optional bool auto_hide_diff_table_header = 14 [default = true]; + optional bool hide_line_numbers = 15; + optional bool render_entire_file = 16; + optional bool hide_empty_pane = 17; + optional bool match_brackets = 18; + optional bool line_wrapping = 19; + + enum Whitespace { + IGNORE_NONE = 0; + IGNORE_TRAILING = 1; + IGNORE_LEADING_AND_TRAILING = 2; + IGNORE_ALL = 3; + } + optional Whitespace ignore_whitespace = 20; + + optional bool retain_header = 21; + optional bool skip_deleted = 22; + optional bool skip_unchanged = 23; + optional bool skip_uncommented = 24; + } + optional DiffPreferencesInfo diff_preferences_info = 2; + + // Next ID: 15 + message EditPreferencesInfo { + optional int32 tab_size = 1 [default = 8]; + optional int32 line_length = 2 [default = 100]; + optional int32 indent_unit = 3 [default = 2]; + optional int32 cursor_blink_rate = 4; + optional bool hide_top_menu = 5; + optional bool show_tabs = 6 [default = true]; + optional bool show_whitespace_errors = 7; + optional bool syntax_highlighting = 8 [default = true]; + optional bool hide_line_numbers = 9; + optional bool match_brackets = 10 [default = true]; + optional bool line_wrapping = 11; + optional bool indent_with_tabs = 12; + optional bool auto_close_brackets = 13; + optional bool show_base = 14; + } + optional EditPreferencesInfo edit_preferences_info = 3; +}