Replace AccountGeneralPreferences entity with git backend (phase 1)

Replace the usage of AccountGeneralPreferences with a different class
in extension API package.

To support live migration, the upgrade is done in two steps:

* phase 1 (this change):
  o Always write to both git and db
  o Introduce new configuration option to indicate whether to read from
    git or db, initially set to read from db
  o First binary update: some servers are reading/writing just the db;
    some servers are additionally writing to git
  o After first update: all servers are reading from the db, writing to
    both
  o Batch copy data from db to git (only related to non open source
    Gerrit version)
  o Update all servers to read from git. During the update, some will
    still be reading from the db; that's ok, because everybody is
    writing to git
  o OutgoingEmail class is retrieving copy_self_on_email preference
    always form the database (by getting AccountGlobalPreferences from
    the account cache). This is OK, because all servers are writing to
    the database
* phase 2 (next change):
  o Bump database version, migrate the data from db to git, delete the
    entity (and the flag) from the code and update the servers.

Change-Id: I218d92465665afa0caa01e88ff1fa87f773383eb
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GeneralPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GeneralPreferencesIT.java
new file mode 100644
index 0000000..e81ee71
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GeneralPreferencesIT.java
@@ -0,0 +1,138 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.account;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
+import com.google.gerrit.extensions.client.MenuItem;
+import com.google.gerrit.testutil.ConfigSuite;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class GeneralPreferencesIT extends AbstractDaemonTest {
+  @ConfigSuite.Config
+  public static Config readFromGitConfig() {
+    Config cfg = new Config();
+    cfg.setBoolean("user", null, "readPrefsFromGit", true);
+    return cfg;
+  }
+
+  private TestAccount user42;
+
+  @Before
+  public void setUp() throws Exception {
+    String name = name("user42");
+    user42 = accounts.create(name, name + "@example.com", "User 42");
+  }
+
+  @Test
+  public void getDiffPreferencesOfNonExistingAccount_NotFound()
+      throws Exception {
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        adminSession.get("/accounts/non-existing/preferences")
+        .getStatusCode());
+  }
+
+  @Test
+  public void getAndSetPreferences() throws Exception {
+    RestResponse r = adminSession.get("/accounts/" + user42.email
+        + "/preferences");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    GeneralPreferencesInfo d = GeneralPreferencesInfo.defaults();
+    GeneralPreferencesInfo o =
+        newGson().fromJson(r.getReader(), GeneralPreferencesInfo.class);
+
+    assertThat(o.changesPerPage).isEqualTo(d.changesPerPage);
+    assertThat(o.showSiteHeader).isEqualTo(d.showSiteHeader);
+    assertThat(o.useFlashClipboard).isEqualTo(d.useFlashClipboard);
+    assertThat(o.downloadScheme).isNull();
+    assertThat(o.downloadCommand).isEqualTo(d.downloadCommand);
+    assertThat(o.dateFormat).isEqualTo(d.getDateFormat());
+    assertThat(o.timeFormat).isEqualTo(d.getTimeFormat());
+    assertThat(o.emailStrategy).isEqualTo(d.getEmailStrategy());
+    assertThat(o.relativeDateInChangeTable).isNull();
+    assertThat(o.sizeBarInChangeTable).isEqualTo(d.sizeBarInChangeTable);
+    assertThat(o.legacycidInChangeTable).isNull();
+    assertThat(o.muteCommonPathPrefixes).isEqualTo(
+        d.muteCommonPathPrefixes);
+    assertThat(o.reviewCategoryStrategy).isEqualTo(
+        d.getReviewCategoryStrategy());
+    assertThat(o.diffView).isEqualTo(d.getDiffView());
+
+    assertThat(o.my).hasSize(7);
+    assertThat(o.urlAliases).isNull();
+
+    GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();
+
+    // change all default values
+    i.changesPerPage *= -1;
+    i.showSiteHeader ^= true;
+    i.useFlashClipboard ^= true;
+    i.downloadCommand = DownloadCommand.REPO_DOWNLOAD;
+    i.dateFormat = DateFormat.US;
+    i.timeFormat = TimeFormat.HHMM_24;
+    i.emailStrategy = EmailStrategy.DISABLED;
+    i.relativeDateInChangeTable ^= true;
+    i.sizeBarInChangeTable ^= true;
+    i.legacycidInChangeTable ^= true;
+    i.muteCommonPathPrefixes ^= true;
+    i.reviewCategoryStrategy = ReviewCategoryStrategy.ABBREV;
+    i.diffView = DiffView.UNIFIED_DIFF;
+    i.my = new ArrayList<>();
+    i.my.add(new MenuItem("name", "url"));
+    i.urlAliases = new HashMap<>();
+    i.urlAliases.put("foo", "bar");
+
+    r = adminSession.put("/accounts/" + user42.email + "/preferences", i);
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    o = newGson().fromJson(r.getReader(), GeneralPreferencesInfo.class);
+
+    assertThat(o.changesPerPage).isEqualTo(i.changesPerPage);
+    assertThat(o.showSiteHeader).isNull();
+    assertThat(o.useFlashClipboard).isNull();
+    assertThat(o.downloadScheme).isNull();
+    assertThat(o.downloadCommand).isEqualTo(i.downloadCommand);
+    assertThat(o.dateFormat).isEqualTo(i.getDateFormat());
+    assertThat(o.timeFormat).isEqualTo(i.getTimeFormat());
+    assertThat(o.emailStrategy).isEqualTo(i.emailStrategy);
+    assertThat(o.relativeDateInChangeTable).isEqualTo(
+        i.relativeDateInChangeTable);
+    assertThat(o.sizeBarInChangeTable).isNull();
+    assertThat(o.legacycidInChangeTable).isEqualTo(i.legacycidInChangeTable);
+    assertThat(o.muteCommonPathPrefixes).isNull();
+    assertThat(o.reviewCategoryStrategy).isEqualTo(
+        i.getReviewCategoryStrategy());
+    assertThat(o.diffView).isEqualTo(i.getDiffView());
+    assertThat(o.my).hasSize(1);
+    assertThat(o.urlAliases).hasSize(1);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
new file mode 100644
index 0000000..8ffcfb9
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -0,0 +1,183 @@
+// Copyright (C) 2015 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.extensions.client;
+
+import java.util.List;
+import java.util.Map;
+
+/** Preferences about a single user. */
+public class GeneralPreferencesInfo {
+
+  /** Default number of items to display per page. */
+  public static final int DEFAULT_PAGESIZE = 25;
+
+  /** Valid choices for the page size. */
+  public static final int[] PAGESIZE_CHOICES = {10, 25, 50, 100};
+
+  /** Preferred method to download a change. */
+  public static enum DownloadCommand {
+    REPO_DOWNLOAD, PULL, CHECKOUT, CHERRY_PICK, FORMAT_PATCH
+  }
+
+  public static enum DateFormat {
+    /** US style dates: Apr 27, Feb 14, 2010 */
+    STD("MMM d", "MMM d, yyyy"),
+
+    /** US style dates: 04/27, 02/14/10 */
+    US("MM/dd", "MM/dd/yy"),
+
+    /** ISO style dates: 2010-02-14 */
+    ISO("MM-dd", "yyyy-MM-dd"),
+
+    /** European style dates: 27. Apr, 27.04.2010 */
+    EURO("d. MMM", "dd.MM.yyyy"),
+
+    /** UK style dates: 27/04, 27/04/2010 */
+    UK("dd/MM", "dd/MM/yyyy");
+
+    private final String shortFormat;
+    private final String longFormat;
+
+    DateFormat(String shortFormat, String longFormat) {
+      this.shortFormat = shortFormat;
+      this.longFormat = longFormat;
+    }
+
+    public String getShortFormat() {
+      return shortFormat;
+    }
+
+    public String getLongFormat() {
+      return longFormat;
+    }
+  }
+
+  public static enum ReviewCategoryStrategy {
+    NONE,
+    NAME,
+    EMAIL,
+    USERNAME,
+    ABBREV
+  }
+
+  public static enum DiffView {
+    SIDE_BY_SIDE,
+    UNIFIED_DIFF
+  }
+
+  public static enum EmailStrategy {
+    ENABLED,
+    CC_ON_OWN_COMMENTS,
+    DISABLED
+  }
+
+  public static enum TimeFormat {
+    /** 12-hour clock: 1:15 am, 2:13 pm */
+    HHMM_12("h:mm a"),
+
+    /** 24-hour clock: 01:15, 14:13 */
+    HHMM_24("HH:mm");
+
+    private final String format;
+
+    TimeFormat(String format) {
+      this.format = format;
+    }
+
+    public String getFormat() {
+      return format;
+    }
+  }
+
+  /** Number of changes to show in a screen. */
+  public Integer changesPerPage;
+  /** Should the site header be displayed when logged in ? */
+  public Boolean showSiteHeader;
+  /** Should the Flash helper movie be used to copy text to the clipboard? */
+  public Boolean useFlashClipboard;
+  /** Type of download URL the user prefers to use. */
+  public String downloadScheme;
+  /** Type of download command the user prefers to use. */
+  public DownloadCommand downloadCommand;
+  public DateFormat dateFormat;
+  public TimeFormat timeFormat;
+  public Boolean relativeDateInChangeTable;
+  public DiffView diffView;
+  public Boolean sizeBarInChangeTable;
+  public Boolean legacycidInChangeTable;
+  public ReviewCategoryStrategy reviewCategoryStrategy;
+  public Boolean muteCommonPathPrefixes;
+  public List<MenuItem> my;
+  public Map<String, String> urlAliases;
+  public EmailStrategy emailStrategy;
+
+  public boolean isShowInfoInReviewCategory() {
+    return getReviewCategoryStrategy() != ReviewCategoryStrategy.NONE;
+  }
+
+  public DateFormat getDateFormat() {
+    if (dateFormat == null) {
+      return DateFormat.STD;
+    }
+    return dateFormat;
+  }
+
+  public TimeFormat getTimeFormat() {
+    if (timeFormat == null) {
+      return TimeFormat.HHMM_12;
+    }
+    return timeFormat;
+  }
+
+  public ReviewCategoryStrategy getReviewCategoryStrategy() {
+    if (reviewCategoryStrategy == null) {
+      return ReviewCategoryStrategy.NONE;
+    }
+    return reviewCategoryStrategy;
+  }
+
+  public DiffView getDiffView() {
+    if (diffView == null) {
+      return DiffView.SIDE_BY_SIDE;
+    }
+    return diffView;
+  }
+
+  public EmailStrategy getEmailStrategy() {
+    if (emailStrategy == null) {
+      return EmailStrategy.ENABLED;
+    }
+    return emailStrategy;
+  }
+
+  public static GeneralPreferencesInfo defaults() {
+    GeneralPreferencesInfo p = new GeneralPreferencesInfo();
+    p.changesPerPage = DEFAULT_PAGESIZE;
+    p.showSiteHeader = true;
+    p.useFlashClipboard = true;
+    p.emailStrategy = EmailStrategy.ENABLED;
+    p.reviewCategoryStrategy = ReviewCategoryStrategy.NONE;
+    p.downloadScheme = null;
+    p.downloadCommand = DownloadCommand.CHECKOUT;
+    p.dateFormat = DateFormat.STD;
+    p.timeFormat = TimeFormat.HHMM_12;
+    p.relativeDateInChangeTable = false;
+    p.diffView = DiffView.SIDE_BY_SIDE;
+    p.sizeBarInChangeTable = true;
+    p.legacycidInChangeTable = false;
+    p.muteCommonPathPrefixes = true;
+    return p;
+  }
+}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java
index 3848f0a..c54fd1c 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java
@@ -17,13 +17,13 @@
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.EmailStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
 
@@ -37,32 +37,32 @@
   }
 
   public static GeneralPreferences createDefault() {
-    AccountGeneralPreferences defaultPrefs =
-        AccountGeneralPreferences.createDefault();
+    GeneralPreferencesInfo d =
+        GeneralPreferencesInfo.defaults();
     GeneralPreferences p = createObject().cast();
-    p.changesPerPage(defaultPrefs.getMaximumPageSize());
-    p.showSiteHeader(defaultPrefs.isShowSiteHeader());
-    p.useFlashClipboard(defaultPrefs.isUseFlashClipboard());
-    p.downloadScheme(defaultPrefs.getDownloadUrl());
-    p.downloadCommand(defaultPrefs.getDownloadCommand());
-    p.dateFormat(defaultPrefs.getDateFormat());
-    p.timeFormat(defaultPrefs.getTimeFormat());
-    p.relativeDateInChangeTable(defaultPrefs.isRelativeDateInChangeTable());
-    p.sizeBarInChangeTable(defaultPrefs.isSizeBarInChangeTable());
-    p.legacycidInChangeTable(defaultPrefs.isLegacycidInChangeTable());
-    p.muteCommonPathPrefixes(defaultPrefs.isMuteCommonPathPrefixes());
-    p.reviewCategoryStrategy(defaultPrefs.getReviewCategoryStrategy());
-    p.diffView(defaultPrefs.getDiffView());
-    p.emailStrategy(defaultPrefs.getEmailStrategy());
+    p.changesPerPage(d.changesPerPage);
+    p.showSiteHeader(d.showSiteHeader);
+    p.useFlashClipboard(d.useFlashClipboard);
+    p.downloadScheme(d.downloadScheme);
+    p.downloadCommand(d.downloadCommand);
+    p.dateFormat(d.getDateFormat());
+    p.timeFormat(d.getTimeFormat());
+    p.relativeDateInChangeTable(d.relativeDateInChangeTable);
+    p.sizeBarInChangeTable(d.sizeBarInChangeTable);
+    p.legacycidInChangeTable(d.legacycidInChangeTable);
+    p.muteCommonPathPrefixes(d.muteCommonPathPrefixes);
+    p.reviewCategoryStrategy(d.getReviewCategoryStrategy());
+    p.diffView(d.getDiffView());
+    p.emailStrategy(d.emailStrategy);
     return p;
   }
 
-  public final short changesPerPage() {
-    short changesPerPage =
-        get("changes_per_page", AccountGeneralPreferences.DEFAULT_PAGESIZE);
+  public final int changesPerPage() {
+    int changesPerPage =
+        get("changes_per_page", GeneralPreferencesInfo.DEFAULT_PAGESIZE);
     return 0 < changesPerPage
         ? changesPerPage
-        : AccountGeneralPreferences.DEFAULT_PAGESIZE;
+        : GeneralPreferencesInfo.DEFAULT_PAGESIZE;
   }
   private final native short get(String n, int d)
   /*-{ return this.hasOwnProperty(n) ? this[n] : d }-*/;
@@ -134,7 +134,7 @@
   public final native JsArray<TopMenuItem> my()
   /*-{ return this.my; }-*/;
 
-  public final native void changesPerPage(short n)
+  public final native void changesPerPage(int n)
   /*-{ this.changes_per_page = n }-*/;
 
   public final native void showSiteHeader(boolean s)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index ba3cc4c..48b217b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -92,8 +92,8 @@
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.java
index 68a99e0..b398c0f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.java
@@ -18,7 +18,7 @@
 
 public interface AccountMessages extends Messages {
   String lines(short cnt);
-  String rowsPerPage(short cnt);
+  String rowsPerPage(int cnt);
   String changeScreenServerDefault(String d);
   String enterIAGREE(String iagree);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index b6322c6..2a00853 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.client.account;
 
-import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DEFAULT_PAGESIZE;
-import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.PAGESIZE_CHOICES;
+import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.DEFAULT_PAGESIZE;
+import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.PAGESIZE_CHOICES;
 
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GerritUiExtensionPoint;
@@ -28,9 +28,8 @@
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.OnEditEnabler;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.EmailStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -71,53 +70,53 @@
     showSiteHeader = new CheckBox(Util.C.showSiteHeader());
     useFlashClipboard = new CheckBox(Util.C.useFlashClipboard());
     maximumPageSize = new ListBox();
-    for (final short v : PAGESIZE_CHOICES) {
+    for (final int v : PAGESIZE_CHOICES) {
       maximumPageSize.addItem(Util.M.rowsPerPage(v), String.valueOf(v));
     }
 
     reviewCategoryStrategy = new ListBox();
     reviewCategoryStrategy.addItem(
         Util.C.messageShowInReviewCategoryNone(),
-        AccountGeneralPreferences.ReviewCategoryStrategy.NONE.name());
+        GeneralPreferencesInfo.ReviewCategoryStrategy.NONE.name());
     reviewCategoryStrategy.addItem(
         Util.C.messageShowInReviewCategoryName(),
-        AccountGeneralPreferences.ReviewCategoryStrategy.NAME.name());
+        GeneralPreferencesInfo.ReviewCategoryStrategy.NAME.name());
     reviewCategoryStrategy.addItem(
         Util.C.messageShowInReviewCategoryEmail(),
-        AccountGeneralPreferences.ReviewCategoryStrategy.EMAIL.name());
+        GeneralPreferencesInfo.ReviewCategoryStrategy.EMAIL.name());
     reviewCategoryStrategy.addItem(
         Util.C.messageShowInReviewCategoryUsername(),
-        AccountGeneralPreferences.ReviewCategoryStrategy.USERNAME.name());
+        GeneralPreferencesInfo.ReviewCategoryStrategy.USERNAME.name());
     reviewCategoryStrategy.addItem(
         Util.C.messageShowInReviewCategoryAbbrev(),
-        AccountGeneralPreferences.ReviewCategoryStrategy.ABBREV.name());
+        GeneralPreferencesInfo.ReviewCategoryStrategy.ABBREV.name());
 
     emailStrategy = new ListBox();
     emailStrategy.addItem(Util.C.messageEnabled(),
-        AccountGeneralPreferences.EmailStrategy.ENABLED.name());
+        GeneralPreferencesInfo.EmailStrategy.ENABLED.name());
     emailStrategy
         .addItem(
             Util.C.messageCCMeOnMyComments(),
-            AccountGeneralPreferences.EmailStrategy.CC_ON_OWN_COMMENTS
+            GeneralPreferencesInfo.EmailStrategy.CC_ON_OWN_COMMENTS
                 .name());
     emailStrategy
         .addItem(
             Util.C.messageDisabled(),
-            AccountGeneralPreferences.EmailStrategy.DISABLED
+            GeneralPreferencesInfo.EmailStrategy.DISABLED
                 .name());
 
     diffView = new ListBox();
     diffView.addItem(
         com.google.gerrit.client.changes.Util.C.sideBySide(),
-        AccountGeneralPreferences.DiffView.SIDE_BY_SIDE.name());
+        GeneralPreferencesInfo.DiffView.SIDE_BY_SIDE.name());
     diffView.addItem(
         com.google.gerrit.client.changes.Util.C.unifiedDiff(),
-        AccountGeneralPreferences.DiffView.UNIFIED_DIFF.name());
+        GeneralPreferencesInfo.DiffView.UNIFIED_DIFF.name());
 
     Date now = new Date();
     dateFormat = new ListBox();
-    for (AccountGeneralPreferences.DateFormat fmt : AccountGeneralPreferences.DateFormat
-        .values()) {
+    for (GeneralPreferencesInfo.DateFormat fmt
+        : GeneralPreferencesInfo.DateFormat.values()) {
       StringBuilder r = new StringBuilder();
       r.append(DateTimeFormat.getFormat(fmt.getShortFormat()).format(now));
       r.append(" ; ");
@@ -126,8 +125,8 @@
     }
 
     timeFormat = new ListBox();
-    for (AccountGeneralPreferences.TimeFormat fmt : AccountGeneralPreferences.TimeFormat
-        .values()) {
+    for (GeneralPreferencesInfo.TimeFormat fmt
+        : GeneralPreferencesInfo.TimeFormat.values()) {
       StringBuilder r = new StringBuilder();
       r.append(DateTimeFormat.getFormat(fmt.getFormat()).format(now));
       timeFormat.addItem(r.toString(), fmt.name());
@@ -270,22 +269,22 @@
     showSiteHeader.setValue(p.showSiteHeader());
     useFlashClipboard.setValue(p.useFlashClipboard());
     setListBox(maximumPageSize, DEFAULT_PAGESIZE, p.changesPerPage());
-    setListBox(dateFormat, AccountGeneralPreferences.DateFormat.STD, //
+    setListBox(dateFormat, GeneralPreferencesInfo.DateFormat.STD, //
         p.dateFormat());
-    setListBox(timeFormat, AccountGeneralPreferences.TimeFormat.HHMM_12, //
+    setListBox(timeFormat, GeneralPreferencesInfo.TimeFormat.HHMM_12, //
         p.timeFormat());
     relativeDateInChangeTable.setValue(p.relativeDateInChangeTable());
     sizeBarInChangeTable.setValue(p.sizeBarInChangeTable());
     legacycidInChangeTable.setValue(p.legacycidInChangeTable());
     muteCommonPathPrefixes.setValue(p.muteCommonPathPrefixes());
     setListBox(reviewCategoryStrategy,
-        AccountGeneralPreferences.ReviewCategoryStrategy.NONE,
+        GeneralPreferencesInfo.ReviewCategoryStrategy.NONE,
         p.reviewCategoryStrategy());
     setListBox(diffView,
-        AccountGeneralPreferences.DiffView.SIDE_BY_SIDE,
+        GeneralPreferencesInfo.DiffView.SIDE_BY_SIDE,
         p.diffView());
     setListBox(emailStrategy,
-        AccountGeneralPreferences.EmailStrategy.ENABLED,
+        GeneralPreferencesInfo.EmailStrategy.ENABLED,
         p.emailStrategy());
     display(p.my());
   }
@@ -298,8 +297,8 @@
     myMenus.display(values);
   }
 
-  private void setListBox(final ListBox f, final short defaultValue,
-      final short currentValue) {
+  private void setListBox(final ListBox f, final int defaultValue,
+      final int currentValue) {
     setListBox(f, String.valueOf(defaultValue), String.valueOf(currentValue));
   }
 
@@ -324,7 +323,7 @@
     }
   }
 
-  private short getListBox(final ListBox f, final short defaultValue) {
+  private int getListBox(final ListBox f, final int defaultValue) {
     final int idx = f.getSelectedIndex();
     if (0 <= idx) {
       return Short.parseShort(f.getValue(idx));
@@ -355,11 +354,11 @@
     p.useFlashClipboard(useFlashClipboard.getValue());
     p.changesPerPage(getListBox(maximumPageSize, DEFAULT_PAGESIZE));
     p.dateFormat(getListBox(dateFormat,
-        AccountGeneralPreferences.DateFormat.STD,
-        AccountGeneralPreferences.DateFormat.values()));
+        GeneralPreferencesInfo.DateFormat.STD,
+        GeneralPreferencesInfo.DateFormat.values()));
     p.timeFormat(getListBox(timeFormat,
-        AccountGeneralPreferences.TimeFormat.HHMM_12,
-        AccountGeneralPreferences.TimeFormat.values()));
+        GeneralPreferencesInfo.TimeFormat.HHMM_12,
+        GeneralPreferencesInfo.TimeFormat.values()));
     p.relativeDateInChangeTable(relativeDateInChangeTable.getValue());
     p.sizeBarInChangeTable(sizeBarInChangeTable.getValue());
     p.legacycidInChangeTable(legacycidInChangeTable.getValue());
@@ -368,10 +367,12 @@
         ReviewCategoryStrategy.NONE,
         ReviewCategoryStrategy.values()));
     p.diffView(getListBox(diffView,
-        AccountGeneralPreferences.DiffView.SIDE_BY_SIDE,
-        AccountGeneralPreferences.DiffView.values()));
+        GeneralPreferencesInfo.DiffView.SIDE_BY_SIDE,
+        GeneralPreferencesInfo.DiffView.values()));
+
     p.emailStrategy(getListBox(emailStrategy,
-        EmailStrategy.ENABLED, EmailStrategy.values()));
+        GeneralPreferencesInfo.EmailStrategy.ENABLED,
+        GeneralPreferencesInfo.EmailStrategy.values()));
 
     List<TopMenuItem> items = new ArrayList<>();
     for (List<String> v : myMenus.getValues()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 6136825..e885498 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -28,8 +28,8 @@
 import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
 import com.google.gerrit.client.ui.ProjectLink;
 import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
 import com.google.gerrit.extensions.client.ListChangesOption;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
index 923a995..97453fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.client.DiffWebLinkInfo;
 import com.google.gerrit.client.info.WebLinkInfo;
 import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
 import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
index 6c500d8..4f2466f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.client.ui.NavigationTable;
 import com.google.gerrit.client.ui.PatchLink;
 import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gerrit.reviewdb.client.Patch.PatchType;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index be743a4..e585be4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -14,22 +14,26 @@
 
 package com.google.gerrit.server.account;
 
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+
 import com.google.common.base.Strings;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
 import com.google.gerrit.extensions.client.MenuItem;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.EmailStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.UserConfigSections;
 import com.google.gwtorm.server.OrmException;
@@ -38,6 +42,7 @@
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -64,133 +69,169 @@
   private final Provider<ReviewDb> db;
   private final AllUsersName allUsersName;
   private final GitRepositoryManager gitMgr;
+  private final boolean readFromGit;
 
   @Inject
-  GetPreferences(Provider<CurrentUser> self, Provider<ReviewDb> db,
+  GetPreferences(Provider<CurrentUser> self,
+      @GerritServerConfig Config cfg,
+      Provider<ReviewDb> db,
       AllUsersName allUsersName,
       GitRepositoryManager gitMgr) {
     this.self = self;
     this.db = db;
     this.allUsersName = allUsersName;
     this.gitMgr = gitMgr;
+    readFromGit = cfg.getBoolean("user", null, "readPrefsFromGit", false);
   }
 
   @Override
-  public PreferenceInfo apply(AccountResource rsrc)
-      throws AuthException,
-      ResourceNotFoundException,
-      OrmException,
-      IOException,
-      ConfigInvalidException {
+  public GeneralPreferencesInfo apply(AccountResource rsrc)
+      throws AuthException, ResourceNotFoundException, OrmException,
+      IOException, ConfigInvalidException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("restricted to administrator");
     }
-    Account a = db.get().accounts().get(rsrc.getUser().getAccountId());
-    if (a == null) {
-      throw new ResourceNotFoundException();
-    }
 
-    try (Repository git = gitMgr.openRepository(allUsersName)) {
+    Account.Id accountId = rsrc.getUser().getAccountId();
+    return readFromGit
+        ? readFromGit(accountId, gitMgr, allUsersName, null)
+        : readFromDb(accountId);
+  }
+
+  private GeneralPreferencesInfo readFromDb(Account.Id accountId)
+      throws IOException, ConfigInvalidException, RepositoryNotFoundException,
+      OrmException {
+    Account a = db.get().accounts().get(accountId);
+    GeneralPreferencesInfo r = nullify(initFromDb(
+        a.getGeneralPreferences()));
+
+    try (Repository allUsers = gitMgr.openRepository(allUsersName)) {
       VersionedAccountPreferences p =
-          VersionedAccountPreferences.forUser(rsrc.getUser().getAccountId());
-      p.load(git);
-      return new PreferenceInfo(a.getGeneralPreferences(), p, git);
+          VersionedAccountPreferences.forUser(accountId);
+      p.load(allUsers);
+
+      return loadFromAllUsers(r, p, allUsers);
     }
   }
 
-  public static class PreferenceInfo {
-    Short changesPerPage;
-    Boolean showSiteHeader;
-    Boolean useFlashClipboard;
-    String downloadScheme;
-    DownloadCommand downloadCommand;
-    DateFormat dateFormat;
-    TimeFormat timeFormat;
-    Boolean relativeDateInChangeTable;
-    Boolean sizeBarInChangeTable;
-    Boolean legacycidInChangeTable;
-    Boolean muteCommonPathPrefixes;
-    ReviewCategoryStrategy reviewCategoryStrategy;
-    DiffView diffView;
-    EmailStrategy emailStrategy;
-    List<MenuItem> my;
-    Map<String, String> urlAliases;
+  public static GeneralPreferencesInfo readFromGit(Account.Id id,
+      GitRepositoryManager gitMgr, AllUsersName allUsersName,
+      GeneralPreferencesInfo in) throws IOException,
+          ConfigInvalidException, RepositoryNotFoundException {
+    try (Repository allUsers = gitMgr.openRepository(allUsersName)) {
+      VersionedAccountPreferences p =
+          VersionedAccountPreferences.forUser(id);
+      p.load(allUsers);
 
-    public PreferenceInfo(AccountGeneralPreferences p,
-        VersionedAccountPreferences v, Repository allUsers) {
-      if (p != null) {
-        changesPerPage = p.getMaximumPageSize();
-        showSiteHeader = p.isShowSiteHeader() ? true : null;
-        useFlashClipboard = p.isUseFlashClipboard() ? true : null;
-        downloadScheme = p.getDownloadUrl();
-        downloadCommand = p.getDownloadCommand();
-        dateFormat = p.getDateFormat();
-        timeFormat = p.getTimeFormat();
-        relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
-        sizeBarInChangeTable = p.isSizeBarInChangeTable() ? true : null;
-        legacycidInChangeTable = p.isLegacycidInChangeTable() ? true : null;
-        muteCommonPathPrefixes = p.isMuteCommonPathPrefixes() ? true : null;
-        reviewCategoryStrategy = p.getReviewCategoryStrategy();
-        diffView = p.getDiffView();
-        emailStrategy = p.getEmailStrategy();
+      GeneralPreferencesInfo r =
+          loadSection(p.getConfig(), UserConfigSections.GENERAL, null,
+          new GeneralPreferencesInfo(),
+          GeneralPreferencesInfo.defaults(), in);
+
+      return loadFromAllUsers(r, p, allUsers);
+    }
+  }
+
+  public static GeneralPreferencesInfo loadFromAllUsers(
+      GeneralPreferencesInfo r, VersionedAccountPreferences v,
+      Repository allUsers) {
+    r.my = my(v);
+    if (r.my.isEmpty() && !v.isDefaults()) {
+      try {
+        VersionedAccountPreferences d = VersionedAccountPreferences.forDefault();
+        d.load(allUsers);
+        r.my = my(d);
+      } catch (ConfigInvalidException | IOException e) {
+        log.warn("cannot read default preferences", e);
       }
-      loadFromAllUsers(v, allUsers);
+    }
+    if (r.my.isEmpty()) {
+      r.my.add(new MenuItem("Changes", "#/dashboard/self", null));
+      r.my.add(new MenuItem("Drafts", "#/q/owner:self+is:draft", null));
+      r.my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
+      r.my.add(new MenuItem("Edits", "#/q/has:edit", null));
+      r.my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open",
+          null));
+      r.my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
+      r.my.add(new MenuItem("Groups", "#/groups/self", null));
     }
 
-    private void loadFromAllUsers(VersionedAccountPreferences v,
-        Repository allUsers) {
-      my = my(v);
-      if (my.isEmpty() && !v.isDefaults()) {
-        try {
-          VersionedAccountPreferences d = VersionedAccountPreferences.forDefault();
-          d.load(allUsers);
-          my = my(d);
-        } catch (ConfigInvalidException | IOException e) {
-          log.warn("cannot read default preferences", e);
-        }
-      }
-      if (my.isEmpty()) {
-        my.add(new MenuItem("Changes", "#/dashboard/self", null));
-        my.add(new MenuItem("Drafts", "#/q/owner:self+is:draft", null));
-        my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
-        my.add(new MenuItem("Edits", "#/q/has:edit", null));
-        my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
-        my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
-        my.add(new MenuItem("Groups", "#/groups/self", null));
-      }
+    r.urlAliases = urlAliases(v);
+    return r;
+  }
 
-      urlAliases = urlAliases(v);
+  private static List<MenuItem> my(VersionedAccountPreferences v) {
+    List<MenuItem> my = new ArrayList<>();
+    Config cfg = v.getConfig();
+    for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
+      String url = my(cfg, subsection, KEY_URL, "#/");
+      String target = my(cfg, subsection, KEY_TARGET,
+          url.startsWith("#") ? null : "_blank");
+      my.add(new MenuItem(
+          subsection, url, target,
+          my(cfg, subsection, KEY_ID, null)));
+    }
+    return my;
+  }
+
+  private static String my(Config cfg, String subsection, String key,
+      String defaultValue) {
+    String val = cfg.getString(UserConfigSections.MY, subsection, key);
+    return !Strings.isNullOrEmpty(val) ? val : defaultValue;
+  }
+
+  private static Map<String, String> urlAliases(VersionedAccountPreferences v) {
+    HashMap<String, String> urlAliases = new HashMap<>();
+    Config cfg = v.getConfig();
+    for (String subsection : cfg.getSubsections(URL_ALIAS)) {
+      urlAliases.put(cfg.getString(URL_ALIAS, subsection, KEY_MATCH),
+         cfg.getString(URL_ALIAS, subsection, KEY_TOKEN));
+    }
+    return !urlAliases.isEmpty() ? urlAliases : null;
+  }
+
+  static GeneralPreferencesInfo initFromDb(AccountGeneralPreferences a) {
+    GeneralPreferencesInfo p = GeneralPreferencesInfo.defaults();
+    if (a != null) {
+      p.changesPerPage = (int)a.getMaximumPageSize();
+      p.showSiteHeader = a.isShowSiteHeader();
+      p.useFlashClipboard = a.isUseFlashClipboard();
+      p.downloadScheme = a.getDownloadUrl();
+      if (a.getDownloadCommand() != null) {
+        p.downloadCommand = DownloadCommand.valueOf(
+            a.getDownloadCommand().name());
+      }
+      p.emailStrategy = EmailStrategy.valueOf(a.getEmailStrategy().name());
+      p.dateFormat = DateFormat.valueOf(a.getDateFormat().name());
+      p.timeFormat = TimeFormat.valueOf(a.getTimeFormat().name());
+      p.relativeDateInChangeTable = a.isRelativeDateInChangeTable();
+      p.sizeBarInChangeTable = a.isSizeBarInChangeTable();
+      p.legacycidInChangeTable = a.isLegacycidInChangeTable();
+      p.muteCommonPathPrefixes = a.isMuteCommonPathPrefixes();
+      p.reviewCategoryStrategy = ReviewCategoryStrategy.valueOf(
+          a.getReviewCategoryStrategy().name());
+      p.diffView = DiffView.valueOf(a.getDiffView().name());
     }
 
-    private List<MenuItem> my(VersionedAccountPreferences v) {
-      List<MenuItem> my = new ArrayList<>();
-      Config cfg = v.getConfig();
-      for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
-        String url = my(cfg, subsection, KEY_URL, "#/");
-        String target = my(cfg, subsection, KEY_TARGET,
-            url.startsWith("#") ? null : "_blank");
-        my.add(new MenuItem(
-            subsection, url, target,
-            my(cfg, subsection, KEY_ID, null)));
-      }
-      return my;
-    }
+    return p;
+  }
 
-    private static String my(Config cfg, String subsection, String key,
-        String defaultValue) {
-      String val = cfg.getString(UserConfigSections.MY, subsection, key);
-      return !Strings.isNullOrEmpty(val) ? val : defaultValue;
-    }
+  private static GeneralPreferencesInfo nullify(
+      GeneralPreferencesInfo p) {
+    p.showSiteHeader = b(p.showSiteHeader);
+    p.useFlashClipboard = b(p.useFlashClipboard);
+    p.relativeDateInChangeTable = b(p.relativeDateInChangeTable);
+    p.legacycidInChangeTable = b(p.legacycidInChangeTable);
+    p.muteCommonPathPrefixes = b(p.muteCommonPathPrefixes);
+    p.sizeBarInChangeTable = b(p.sizeBarInChangeTable);
+    return p;
+  }
 
-    private static Map<String, String> urlAliases(VersionedAccountPreferences v) {
-      HashMap<String, String> urlAliases = new HashMap<>();
-      Config cfg = v.getConfig();
-      for (String subsection : cfg.getSubsections(URL_ALIAS)) {
-        urlAliases.put(cfg.getString(URL_ALIAS, subsection, KEY_MATCH),
-           cfg.getString(URL_ALIAS, subsection, KEY_TOKEN));
-      }
-      return !urlAliases.isEmpty() ? urlAliases : null;
+  private static Boolean b(Boolean b) {
+    if (b == null) {
+      return null;
     }
+    return b ? Boolean.TRUE : null;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index 5d83c13..ff4a6a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -20,8 +20,12 @@
 import static com.google.gerrit.server.account.GetPreferences.KEY_TOKEN;
 import static com.google.gerrit.server.account.GetPreferences.KEY_URL;
 import static com.google.gerrit.server.account.GetPreferences.URL_ALIAS;
+import static com.google.gerrit.server.account.GetPreferences.initFromDb;
+import static com.google.gerrit.server.account.GetPreferences.readFromGit;
+import static com.google.gerrit.server.config.ConfigUtil.storeSection;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.client.MenuItem;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -39,8 +43,9 @@
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.SetPreferences.Input;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.UserConfigSections;
 import com.google.gwtorm.server.OrmException;
@@ -49,6 +54,7 @@
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Config;
 
 import java.io.IOException;
@@ -58,130 +64,104 @@
 import java.util.Map.Entry;
 
 @Singleton
-public class SetPreferences implements RestModifyView<AccountResource, Input> {
-  public static class Input {
-    public Short changesPerPage;
-    public Boolean showSiteHeader;
-    public Boolean useFlashClipboard;
-    public String downloadScheme;
-    public DownloadCommand downloadCommand;
-    public DateFormat dateFormat;
-    public TimeFormat timeFormat;
-    public Boolean relativeDateInChangeTable;
-    public Boolean sizeBarInChangeTable;
-    public Boolean legacycidInChangeTable;
-    public Boolean muteCommonPathPrefixes;
-    public ReviewCategoryStrategy reviewCategoryStrategy;
-    public DiffView diffView;
-    public EmailStrategy emailStrategy;
-    public List<MenuItem> my;
-    public Map<String, String> urlAliases;
-  }
-
+public class SetPreferences implements
+    RestModifyView<AccountResource, GeneralPreferencesInfo> {
   private final Provider<CurrentUser> self;
   private final AccountCache cache;
+  private final GitRepositoryManager gitMgr;
   private final Provider<ReviewDb> db;
   private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
   private final AllUsersName allUsersName;
   private final DynamicMap<DownloadScheme> downloadSchemes;
+  private final boolean readFromGit;
 
   @Inject
   SetPreferences(Provider<CurrentUser> self,
       AccountCache cache,
+      @GerritServerConfig Config cfg,
+      GitRepositoryManager gitMgr,
       Provider<ReviewDb> db,
       Provider<MetaDataUpdate.User> metaDataUpdateFactory,
       AllUsersName allUsersName,
       DynamicMap<DownloadScheme> downloadSchemes) {
     this.self = self;
     this.cache = cache;
+    this.gitMgr = gitMgr;
     this.db = db;
     this.metaDataUpdateFactory = metaDataUpdateFactory;
     this.allUsersName = allUsersName;
     this.downloadSchemes = downloadSchemes;
+    readFromGit = cfg.getBoolean("user", null, "readPrefsFromGit", false);
   }
 
   @Override
-  public GetPreferences.PreferenceInfo apply(AccountResource rsrc, Input i)
-      throws AuthException, ResourceNotFoundException, BadRequestException,
-      OrmException, IOException, ConfigInvalidException {
+  public GeneralPreferencesInfo apply(AccountResource rsrc,
+      GeneralPreferencesInfo i)
+          throws AuthException, ResourceNotFoundException, BadRequestException,
+          OrmException, IOException, ConfigInvalidException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canModifyAccount()) {
       throw new AuthException("restricted to members of Modify Accounts");
     }
-    if (i == null) {
-      i = new Input();
-    }
 
-    Account.Id accountId = rsrc.getUser().getAccountId();
-    AccountGeneralPreferences p;
-    VersionedAccountPreferences versionedPrefs;
-    db.get().accounts().beginTransaction(accountId);
+    checkDownloadScheme(i.downloadScheme);
+    Account.Id id = rsrc.getUser().getAccountId();
+    GeneralPreferencesInfo n = readFromGit
+        ? readFromGit(id, gitMgr, allUsersName, i)
+        : merge(initFromDb(
+            db.get().accounts().get(id).getGeneralPreferences()), i);
+
+    n.my = i.my;
+    n.urlAliases = i.urlAliases;
+
+    writeToGit(id, n);
+    writeToDb(id, n);
+    cache.evict(id);
+
+    return GetPreferences.readFromGit(id, gitMgr, allUsersName, null);
+  }
+
+  private void writeToGit(Account.Id id, GeneralPreferencesInfo i)
+      throws RepositoryNotFoundException, IOException, ConfigInvalidException {
+    VersionedAccountPreferences prefs;
     try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
-      Account a = db.get().accounts().get(accountId);
-      if (a == null) {
-        throw new ResourceNotFoundException();
-      }
+      prefs = VersionedAccountPreferences.forUser(id);
+      prefs.load(md);
 
-      versionedPrefs = VersionedAccountPreferences.forUser(accountId);
+      storeSection(prefs.getConfig(), UserConfigSections.GENERAL, null, i,
+          GeneralPreferencesInfo.defaults());
+
+      storeMyMenus(prefs, i.my);
+      storeUrlAliases(prefs, i.urlAliases);
+      prefs.commit(md);
+    }
+  }
+
+  private void writeToDb(Account.Id id, GeneralPreferencesInfo i)
+      throws RepositoryNotFoundException, IOException, OrmException,
+      ConfigInvalidException {
+    MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName);
+    db.get().accounts().beginTransaction(id);
+    try {
+      Account a = db.get().accounts().get(id);
+
+      VersionedAccountPreferences versionedPrefs =
+          VersionedAccountPreferences.forUser(id);
       versionedPrefs.load(md);
 
-      p = a.getGeneralPreferences();
+      AccountGeneralPreferences p = a.getGeneralPreferences();
       if (p == null) {
         p = new AccountGeneralPreferences();
         a.setGeneralPreferences(p);
       }
 
-      if (i.changesPerPage != null) {
-        p.setMaximumPageSize(i.changesPerPage);
-      }
-      if (i.showSiteHeader != null) {
-        p.setShowSiteHeader(i.showSiteHeader);
-      }
-      if (i.useFlashClipboard != null) {
-        p.setUseFlashClipboard(i.useFlashClipboard);
-      }
-      if (i.downloadScheme != null) {
-        setDownloadScheme(p, i.downloadScheme);
-      }
-      if (i.downloadCommand != null) {
-        p.setDownloadCommand(i.downloadCommand);
-      }
-      if (i.dateFormat != null) {
-        p.setDateFormat(i.dateFormat);
-      }
-      if (i.timeFormat != null) {
-        p.setTimeFormat(i.timeFormat);
-      }
-      if (i.relativeDateInChangeTable != null) {
-        p.setRelativeDateInChangeTable(i.relativeDateInChangeTable);
-      }
-      if (i.sizeBarInChangeTable != null) {
-        p.setSizeBarInChangeTable(i.sizeBarInChangeTable);
-      }
-      if (i.legacycidInChangeTable != null) {
-        p.setLegacycidInChangeTable(i.legacycidInChangeTable);
-      }
-      if (i.muteCommonPathPrefixes != null) {
-        p.setMuteCommonPathPrefixes(i.muteCommonPathPrefixes);
-      }
-      if (i.reviewCategoryStrategy != null) {
-        p.setReviewCategoryStrategy(i.reviewCategoryStrategy);
-      }
-      if (i.diffView != null) {
-        p.setDiffView(i.diffView);
-      }
-      if (i.emailStrategy != null) {
-        p.setEmailStrategy(i.emailStrategy);
-      }
+      initAccountGeneralPreferences(p, i);
 
       db.get().accounts().update(Collections.singleton(a));
       db.get().commit();
       storeMyMenus(versionedPrefs, i.my);
       storeUrlAliases(versionedPrefs, i.urlAliases);
       versionedPrefs.commit(md);
-      cache.evict(accountId);
-      return new GetPreferences.PreferenceInfo(
-          p, versionedPrefs, md.getRepository());
     } finally {
       db.get().rollback();
     }
@@ -232,15 +212,96 @@
     }
   }
 
-  private void setDownloadScheme(AccountGeneralPreferences p, String scheme)
+  private void checkDownloadScheme(String downloadScheme)
       throws BadRequestException {
+    if (Strings.isNullOrEmpty(downloadScheme)) {
+      return;
+    }
+
     for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
-      if (e.getExportName().equals(scheme)
+      if (e.getExportName().equals(downloadScheme)
           && e.getProvider().get().isEnabled()) {
-        p.setDownloadUrl(scheme);
         return;
       }
     }
-    throw new BadRequestException("Unsupported download scheme: " + scheme);
+    throw new BadRequestException(
+        "Unsupported download scheme: " + downloadScheme);
+  }
+
+  private GeneralPreferencesInfo merge(GeneralPreferencesInfo p,
+      GeneralPreferencesInfo i) {
+    if (i.changesPerPage != null) {
+      p.changesPerPage = i.changesPerPage;
+    }
+    if (i.showSiteHeader != null) {
+      p.showSiteHeader = i.showSiteHeader;
+    }
+    if (i.useFlashClipboard != null) {
+      p.useFlashClipboard = i.useFlashClipboard;
+    }
+    if (i.downloadScheme != null) {
+      p.downloadScheme = i.downloadScheme;
+    }
+    if (i.downloadCommand != null) {
+      p.downloadCommand = i.downloadCommand;
+    }
+    if (i.dateFormat != null) {
+      p.dateFormat = i.dateFormat;
+    }
+    if (i.timeFormat != null) {
+      p.timeFormat = i.timeFormat;
+    }
+    if (i.relativeDateInChangeTable != null) {
+      p.relativeDateInChangeTable = i.relativeDateInChangeTable;
+    }
+    if (i.sizeBarInChangeTable != null) {
+      p.sizeBarInChangeTable = i.sizeBarInChangeTable;
+    }
+    if (i.legacycidInChangeTable != null) {
+      p.legacycidInChangeTable = i.legacycidInChangeTable;
+    }
+    if (i.muteCommonPathPrefixes != null) {
+      p.muteCommonPathPrefixes = i.muteCommonPathPrefixes;
+    }
+    if (i.reviewCategoryStrategy != null) {
+      p.reviewCategoryStrategy = i.reviewCategoryStrategy;
+    }
+    if (i.diffView != null) {
+      p.diffView = i.diffView;
+    }
+    if (i.emailStrategy != null) {
+      p.emailStrategy = i.emailStrategy;
+    }
+
+    return p;
+  }
+
+  private static void initAccountGeneralPreferences(
+      AccountGeneralPreferences a, GeneralPreferencesInfo i) {
+    if (a == null) {
+      a = AccountGeneralPreferences.createDefault();
+    }
+
+    a.setMaximumPageSize((short)(int)i.changesPerPage);
+    a.setShowSiteHeader(b(i.showSiteHeader));
+    a.setUseFlashClipboard(b(i.useFlashClipboard));
+    a.setDownloadUrl(i.downloadScheme);
+    if (i.downloadCommand != null) {
+      a.setDownloadCommand(DownloadCommand.valueOf(i.downloadCommand.name()));
+    }
+    a.setEmailStrategy(EmailStrategy.valueOf(i.getEmailStrategy().name()));
+    a.setDateFormat(DateFormat.valueOf(i.getDateFormat().name()));
+    a.setTimeFormat(TimeFormat.valueOf(i.getTimeFormat().name()));
+    a.setRelativeDateInChangeTable(b(i.relativeDateInChangeTable));
+    a.setSizeBarInChangeTable(b(i.sizeBarInChangeTable));
+    a.setLegacycidInChangeTable(b(i.legacycidInChangeTable));
+    a.setMuteCommonPathPrefixes(b(i.muteCommonPathPrefixes));
+    a.setReviewCategoryStrategy(ReviewCategoryStrategy.valueOf(
+        i.getReviewCategoryStrategy().name()));
+    a.setDiffView(DiffView.valueOf(i.getDiffView().name()));
+  }
+
+  private static boolean b(Boolean b) {
+    return b == null ? false : b;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java
index f435a2b..c1c5910 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.config;
 
 import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
 import com.google.gerrit.reviewdb.client.CoreDownloadSchemes;
 import com.google.gerrit.server.change.ArchiveFormat;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java
index d120275..5158abd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.server.config;
 
+import static com.google.gerrit.server.account.GetPreferences.loadFromAllUsers;
+
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.GetPreferences.PreferenceInfo;
 import com.google.gerrit.server.account.VersionedAccountPreferences;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
@@ -39,13 +41,15 @@
   }
 
   @Override
-  public PreferenceInfo apply(ConfigResource rsrc)
+  public GeneralPreferencesInfo apply(ConfigResource rsrc)
       throws IOException, ConfigInvalidException {
     try (Repository git = gitMgr.openRepository(allUsersName)) {
       VersionedAccountPreferences p =
           VersionedAccountPreferences.forDefault();
       p.load(git);
-      return new PreferenceInfo(null, p, git);
+
+      GeneralPreferencesInfo a = new GeneralPreferencesInfo();
+      return loadFromAllUsers(a, p, git);
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
index 07d22b3..12c0f8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
@@ -14,12 +14,13 @@
 
 package com.google.gerrit.server.config;
 
+import static com.google.gerrit.server.account.GetPreferences.loadFromAllUsers;
+
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.account.GetPreferences.PreferenceInfo;
-import com.google.gerrit.server.account.SetPreferences.Input;
 import com.google.gerrit.server.account.VersionedAccountPreferences;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.inject.Inject;
@@ -32,7 +33,8 @@
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
 @Singleton
-public class SetPreferences implements RestModifyView<ConfigResource, Input> {
+public class SetPreferences implements
+    RestModifyView<ConfigResource, GeneralPreferencesInfo> {
   private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
   private final AllUsersName allUsersName;
 
@@ -44,8 +46,9 @@
   }
 
   @Override
-  public Object apply(ConfigResource rsrc, Input i) throws BadRequestException,
-      IOException, ConfigInvalidException {
+  public GeneralPreferencesInfo apply(ConfigResource rsrc,
+      GeneralPreferencesInfo i)
+          throws BadRequestException, IOException, ConfigInvalidException {
     if (i.changesPerPage != null || i.showSiteHeader != null
         || i.useFlashClipboard != null || i.downloadScheme != null
         || i.downloadCommand != null
@@ -65,7 +68,9 @@
       p.load(md);
       com.google.gerrit.server.account.SetPreferences.storeMyMenus(p, i.my);
       p.commit(md);
-      return new PreferenceInfo(null, p, md.getRepository());
+
+      GeneralPreferencesInfo a = new GeneralPreferencesInfo();
+      return loadFromAllUsers(a, p, md.getRepository());
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java
index a1c5b8a..78431ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java
@@ -16,6 +16,9 @@
 
 public class UserConfigSections {
 
+  /** The general user preferences. */
+  public static final String GENERAL = "general";
+
   /** The my menu user preferences. */
   public static final String MY = "my";