Merge "Fix account visibility bypass and PII leak in QueryAccounts"
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 92e8963..ceca34b 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -8,6 +8,7 @@
 --
 _ssh_ -p <port> <host> _gerrit show-caches_
   [--show-jvm]
+  [--include-diskstats]
   [--cache <NAME> ...]
 --
 
@@ -25,6 +26,9 @@
 	operating system, and other details about the environment
 	that Gerrit Code Review is running in.
 
+--include-diskstats::
+	Include disk stat collection for persistent caches.
+
 --show-threads::
 	Show detailed counts for Gerrit specific threads.
 
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index b86d732..f7e477d 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -330,11 +330,25 @@
 
 The entries in the map are sorted by cache name.
 
+.Query Options
+[options="header",cols="1,3"]
+|===
+|Field Name          |Description
+|`include-diskstats` |If `true`, disk stat collection is included for persistent caches.
+|===
+
 .Request
 ----
   GET /config/server/caches/ HTTP/1.0
 ----
 
+To include disk stats for persistent caches:
+
+.Request
+----
+  GET /config/server/caches/?include-diskstats=true HTTP/1.0
+----
+
 .Response
 ----
   HTTP/1.1 200 OK
diff --git a/contrib/maintenance/gerrit/site.py b/contrib/maintenance/gerrit/site.py
index faf6c02..450acaa 100644
--- a/contrib/maintenance/gerrit/site.py
+++ b/contrib/maintenance/gerrit/site.py
@@ -37,11 +37,11 @@
             ) as cfg:
                 config_base_path = cfg.get("gerrit", None, "basePath", "git")
                 if os.path.isabs(config_base_path):
-                    self.basePath = config_base_path
+                    self.base_path = config_base_path
                 else:
-                    self.basePath = os.path.join(self.path, config_base_path)
+                    self.base_path = os.path.join(self.path, config_base_path)
 
-        return self.basePath
+        return self.base_path
 
     def get_projects(self, excludes=None):
         for current, dirs, _ in os.walk(self.get_base_path(), topdown=True):
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index 7c0f258..0964dae 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -315,7 +315,7 @@
       new CacheDisplay(
               sw,
               StreamSupport.stream(cacheMap.spliterator(), false)
-                  .map(e -> CacheInfoFactory.create(e.getExportName(), e.get()))
+                  .map(e -> CacheInfoFactory.create(e.getExportName(), e.get(), true))
                   .collect(Collectors.toList()))
           .displayCaches();
       System.out.print(sw.toString());
diff --git a/java/com/google/gerrit/server/cache/CacheInfoFactory.java b/java/com/google/gerrit/server/cache/CacheInfoFactory.java
index dafa186..48122db 100644
--- a/java/com/google/gerrit/server/cache/CacheInfoFactory.java
+++ b/java/com/google/gerrit/server/cache/CacheInfoFactory.java
@@ -24,11 +24,11 @@
 
 public class CacheInfoFactory {
 
-  public static CacheInfo create(Cache<?, ?> cache) {
-    return create(null, cache);
+  public static CacheInfo create(Cache<?, ?> cache, boolean includeDiskStats) {
+    return create(null, cache, includeDiskStats);
   }
 
-  public static CacheInfo create(String name, Cache<?, ?> cache) {
+  public static CacheInfo create(String name, Cache<?, ?> cache, boolean includeDiskStats) {
     CacheInfo cacheInfo = new CacheInfo();
     cacheInfo.name = name;
 
@@ -44,10 +44,12 @@
 
     if (cache instanceof PersistentCache) {
       cacheInfo.type = CacheType.DISK;
-      PersistentCache.DiskStats diskStats = ((PersistentCache) cache).diskStats();
-      cacheInfo.entries.setDisk(diskStats.size());
-      cacheInfo.entries.setSpace(diskStats.space());
-      cacheInfo.hitRatio.setDisk(diskStats.hitCount(), diskStats.requestCount());
+      if (includeDiskStats) {
+        PersistentCache.DiskStats diskStats = ((PersistentCache) cache).diskStats();
+        cacheInfo.entries.setDisk(diskStats.size());
+        cacheInfo.entries.setSpace(diskStats.space());
+        cacheInfo.hitRatio.setDisk(diskStats.hitCount(), diskStats.requestCount());
+      }
     } else {
       cacheInfo.type = CacheType.MEM;
     }
diff --git a/java/com/google/gerrit/server/restapi/config/GetCache.java b/java/com/google/gerrit/server/restapi/config/GetCache.java
index 23615fa..0f06128 100644
--- a/java/com/google/gerrit/server/restapi/config/GetCache.java
+++ b/java/com/google/gerrit/server/restapi/config/GetCache.java
@@ -26,6 +26,6 @@
 
   @Override
   public Response<CacheInfo> apply(CacheResource rsrc) {
-    return Response.ok(CacheInfoFactory.create(rsrc.getName(), rsrc.getCache()));
+    return Response.ok(CacheInfoFactory.create(rsrc.getName(), rsrc.getCache(), true));
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/config/ListCaches.java b/java/com/google/gerrit/server/restapi/config/ListCaches.java
index 2856d83..6411615 100644
--- a/java/com/google/gerrit/server/restapi/config/ListCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCaches.java
@@ -51,26 +51,33 @@
   @Option(name = "--format", usage = "output format")
   private OutputFormat format;
 
+  @Option(
+      name = "--include-diskstats",
+      usage = "if set, disk stat collection is included for persistent caches")
+  private boolean includeDiskStats;
+
   public ListCaches setFormat(OutputFormat format) {
     this.format = format;
     return this;
   }
 
+  public ListCaches setIncludeDiskStats(boolean includeDiskStats) {
+    this.includeDiskStats = includeDiskStats;
+    return this;
+  }
+
   @Inject
   public ListCaches(DynamicMap<Cache<?, ?>> cacheMap) {
     this.cacheMap = cacheMap;
   }
 
-  public Map<String, CacheInfo> getCacheInfos() {
-    return getCacheInfos(name -> true);
-  }
-
-  public Map<String, CacheInfo> getCacheInfos(Predicate<String> nameFilter) {
+  public Map<String, CacheInfo> getCacheInfos(
+      Predicate<String> nameFilter, boolean includeDiskStats) {
     Map<String, CacheInfo> cacheInfos = new TreeMap<>();
     for (Extension<Cache<?, ?>> e : cacheMap) {
       String name = cacheNameOf(e.getPluginName(), e.getExportName());
       if (nameFilter.test(name)) {
-        cacheInfos.put(name, CacheInfoFactory.create(e.getProvider().get()));
+        cacheInfos.put(name, CacheInfoFactory.create(e.getProvider().get(), includeDiskStats));
       }
     }
     return cacheInfos;
@@ -79,7 +86,7 @@
   @Override
   public Response<Object> apply(ConfigResource rsrc) {
     if (format == null) {
-      return Response.ok(getCacheInfos());
+      return Response.ok(getCacheInfos(name -> true, includeDiskStats));
     }
     Stream<String> cacheNames =
         Streams.stream(cacheMap)
diff --git a/java/com/google/gerrit/sshd/commands/ShowCaches.java b/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 71e5bd1..bf7c154 100644
--- a/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -85,6 +85,11 @@
   private boolean showThreads;
 
   @Option(
+      name = "--include-diskstats",
+      usage = "include disk stat collection for persistent caches")
+  private boolean includeDiskStats;
+
+  @Option(
       name = "--cache",
       usage = "show the named cache; may be supplied more than once",
       metaVar = "NAME")
@@ -166,11 +171,13 @@
   private Collection<CacheInfo> getCaches() {
     Map<String, CacheInfo> selected;
     if (caches.isEmpty()) {
-      selected = listCaches.getCacheInfos();
+      selected = listCaches.getCacheInfos(name -> true, includeDiskStats);
     } else {
       Set<String> filter =
           caches.stream().map(n -> n.toLowerCase(Locale.US)).collect(Collectors.toSet());
-      selected = listCaches.getCacheInfos(n -> filter.contains(n.toLowerCase(Locale.US)));
+      selected =
+          listCaches.getCacheInfos(
+              n -> filter.contains(n.toLowerCase(Locale.US)), includeDiskStats);
     }
     for (Map.Entry<String, CacheInfo> entry : selected.entrySet()) {
       CacheInfo cache = entry.getValue();
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
index a987225..7f2e39e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
@@ -21,6 +21,7 @@
 import com.google.common.io.BaseEncoding;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.extensions.common.CacheInfo;
 import com.google.gson.reflect.TypeToken;
 import java.util.Arrays;
@@ -88,4 +89,36 @@
   public void listCaches_BadRequest() throws Exception {
     adminRestSession.get("/config/server/caches/?format=NONSENSE").assertBadRequest();
   }
+
+  @Test
+  public void listCaches_withoutIncludeDiskStats_memCacheUnaffected() throws Exception {
+    RestResponse r = adminRestSession.get("/config/server/caches/");
+    r.assertOK();
+    Map<String, CacheInfo> result =
+        newGson().fromJson(r.getReader(), new TypeToken<Map<String, CacheInfo>>() {}.getType());
+
+    assertThat(result).containsKey("accounts");
+    CacheInfo accountsCacheInfo = result.get("accounts");
+    assertThat(accountsCacheInfo.type).isEqualTo(CacheInfo.CacheType.MEM);
+    assertThat(accountsCacheInfo.entries.mem).isAtLeast(1L);
+    assertThat(accountsCacheInfo.hitRatio.mem).isAtLeast(0);
+
+    assertThat(accountsCacheInfo.entries.disk).isNull();
+    assertThat(accountsCacheInfo.hitRatio.disk).isNull();
+  }
+
+  @Test
+  @UseLocalDisk
+  public void listCaches_withIncludeDiskStats_diskCacheHasDiskStats() throws Exception {
+    RestResponse r = adminRestSession.get("/config/server/caches/?include-diskstats=true");
+    r.assertOK();
+    Map<String, CacheInfo> result =
+        newGson().fromJson(r.getReader(), new TypeToken<Map<String, CacheInfo>>() {}.getType());
+
+    assertThat(result).containsKey("accounts");
+    CacheInfo accountsInfo = result.get("accounts");
+    assertThat(accountsInfo.type).isEqualTo(CacheInfo.CacheType.DISK);
+    assertThat(accountsInfo.entries.mem).isNotNull();
+    assertThat(accountsInfo.entries.disk).isNotNull();
+  }
 }
diff --git a/plugins/replication b/plugins/replication
index 0f4eec7..54898d7 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 0f4eec70496e7589dba4d7e432deb427c22b6069
+Subproject commit 54898d71ffa30bcdaa0f37f968efb0985d9bca64
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index bfdde07..a9284c5 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -29,7 +29,11 @@
 import {subscribe} from '../../lit/subscription-controller';
 import {assert} from '../../../utils/common-util';
 import {resolve} from '../../../models/dependency';
-import {createChangeUrl} from '../../../models/views/change';
+import {
+  changeViewModelToken,
+  createApplyFixUrl,
+} from '../../../models/views/change';
+
 import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
 import {userModelToken} from '../../../models/user/user-model';
 import {modalStyles} from '../../../styles/gr-modal-styles';
@@ -114,6 +118,8 @@
 
   private readonly getNavigation = resolve(this, navigationToken);
 
+  private readonly getViewModel = resolve(this, changeViewModelToken);
+
   private readonly reporting = getAppContext().reportingService;
 
   private readonly syntaxLayer = new GrSyntaxLayerWorker(
@@ -481,16 +487,22 @@
       });
     }
     if (res?.ok) {
+      const currentChildView = this.getViewModel().getState()?.childView;
+      const filePath =
+        fixSuggestion.replacements[0]?.path ??
+        this.currentPreviews[0]?.filepath;
       this.getNavigation().setUrl(
-        createChangeUrl({
+        createApplyFixUrl({
           change,
-          patchNum: EDIT,
           basePatchNum: patchNum as BasePatchSetNum,
           forceReload: !this.hasEdit,
+          filePath,
+          currentChildView,
         })
       );
       this.close(true);
     }
+
     this.isApplyFixLoading = false;
   }
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
index 1d35430..21ce8ed 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
@@ -14,12 +14,15 @@
 import {GrApplyFixDialog} from './gr-apply-fix-dialog';
 import {PatchSetNum, PatchSetNumber} from '../../../types/common';
 import {
+  createChangeViewState,
+  createDiffViewState,
   createFixSuggestionInfo,
   createParsedChange,
   createRange,
   createRevisions,
   getCurrentRevision,
 } from '../../../test/test-data-generators';
+import {changeViewModelToken} from '../../../models/views/change';
 import {createDefaultDiffPrefs} from '../../../constants/constants';
 import {OpenFixPreviewEventDetail} from '../../../types/events';
 import {GrButton} from '../../shared/gr-button/gr-button';
@@ -227,4 +230,70 @@
       '/c/test-project/+/42/2..edit?forceReload=true'
     );
   });
+
+  suite('handleApplyFix navigation', () => {
+    setup(() => {
+      stubRestApi('applyFixSuggestion').returns(
+        Promise.resolve(new Response(null, {status: 200}))
+      );
+    });
+
+    test('navigates to createDiffUrl when in Diff View', async () => {
+      testResolver(changeViewModelToken).setState(createDiffViewState());
+      const fixDetail: OpenFixPreviewEventDetail = {
+        patchNum: 2 as PatchSetNum,
+        fixSuggestions: [
+          {
+            ...createFixSuggestionInfo('fix_1'),
+            replacements: [
+              {
+                path: 'file1.txt',
+                replacement: 'new content',
+                range: createRange(),
+              },
+            ],
+          },
+        ],
+        onCloseFixPreviewCallbacks: [],
+      };
+      await open(fixDetail);
+
+      await element.handleApplyFix(new CustomEvent('confirm'));
+
+      assert.isTrue(setUrlStub.calledOnce);
+      assert.equal(
+        setUrlStub.lastCall.firstArg,
+        '/c/test-project/+/42/2..edit/file1.txt?forceReload=true'
+      );
+    });
+
+    test('navigates to createChangeUrl when in Change View', async () => {
+      testResolver(changeViewModelToken).setState(createChangeViewState());
+      const fixDetail: OpenFixPreviewEventDetail = {
+        patchNum: 2 as PatchSetNum,
+        fixSuggestions: [
+          {
+            ...createFixSuggestionInfo('fix_1'),
+            replacements: [
+              {
+                path: 'file1.txt',
+                replacement: 'new content',
+                range: createRange(),
+              },
+            ],
+          },
+        ],
+        onCloseFixPreviewCallbacks: [],
+      };
+      await open(fixDetail);
+
+      await element.handleApplyFix(new CustomEvent('confirm'));
+
+      assert.isTrue(setUrlStub.calledOnce);
+      assert.equal(
+        setUrlStub.lastCall.firstArg,
+        '/c/test-project/+/42/2..edit?forceReload=true'
+      );
+    });
+  });
 });
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
index 72e0905..0b46706 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
@@ -30,7 +30,11 @@
 import {navigationToken} from '../../core/gr-navigation/gr-navigation';
 import {fire, fireError} from '../../../utils/event-util';
 import {Timing} from '../../../constants/reporting';
-import {createChangeUrl} from '../../../models/views/change';
+import {
+  changeViewModelToken,
+  createApplyFixUrl,
+} from '../../../models/views/change';
+
 import {getFileExtension} from '../../../utils/file-util';
 import {throwingErrorCallback} from '../gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
 import {ReportSource} from '../../../services/suggestions/suggestions-service';
@@ -128,6 +132,8 @@
 
   private readonly getNavigation = resolve(this, navigationToken);
 
+  private readonly getViewModel = resolve(this, changeViewModelToken);
+
   private readonly syntaxLayer = new GrSyntaxLayerWorker(
     resolve(this, highlightServiceToken),
     () => getAppContext().reportingService
@@ -391,16 +397,23 @@
     // basePatchNum is from comment patchset and comment cannot be created
     // in EDIT. RevisionPatchset without EDIT is PatchSetNumber
     if (res?.ok && basePatchNum !== undefined && basePatchNum !== EDIT) {
+      const currentChildView = this.getViewModel().getState()?.childView;
+      const filePath =
+        fixSuggestion.replacements[0]?.path ?? this.preview?.filepath;
       this.getNavigation().setUrl(
-        createChangeUrl({
+        createApplyFixUrl({
           changeNum,
           repo: this.repo!,
-          patchNum: EDIT,
           basePatchNum: basePatchNum as PatchSetNumber,
           forceReload: !this.hasEdit,
+          filePath,
+          currentChildView,
         })
       );
-      fire(this, 'reload-diff', {path: fixSuggestion.replacements[0].path});
+
+      if (filePath) {
+        fire(this, 'reload-diff', {path: filePath});
+      }
       fire(this, 'apply-user-suggestion', {
         fixSuggestion: fixSuggestion.description.includes(
           ReportSource.GET_AI_FIX_FOR_COMMENT
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts
index 742900f..da0220c 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts
@@ -17,8 +17,21 @@
 } from '../../../test/test-data-generators';
 import {getAppContext} from '../../../services/app-context';
 import {GrSuggestionDiffPreview} from './gr-suggestion-diff-preview';
-import {stubFlags} from '../../../test/test-utils';
-import {NumericChangeId, RevisionPatchSetNum} from '../../../api/rest-api';
+import * as sinon from 'sinon';
+import {navigationToken} from '../../core/gr-navigation/gr-navigation';
+import {stubFlags, stubRestApi} from '../../../test/test-utils';
+import {
+  NumericChangeId,
+  RepoName,
+  RevisionPatchSetNum,
+} from '../../../api/rest-api';
+import {changeViewModelToken} from '../../../models/views/change';
+import {
+  createChangeViewState,
+  createDiffViewState,
+  createRange,
+} from '../../../test/test-data-generators';
+import {testResolver} from '../../../test/common-test-setup';
 
 suite('gr-suggestion-diff-preview tests', () => {
   let element: GrSuggestionDiffPreview;
@@ -121,4 +134,53 @@
       {ignoreAttributes: ['style']}
     );
   });
+
+  suite('applyFix navigation', () => {
+    let setUrlStub: sinon.SinonStub;
+
+    setup(() => {
+      setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
+      stubRestApi('applyFixSuggestion').returns(
+        Promise.resolve(new Response(null, {status: 200}))
+      );
+      element.changeNum = 42 as NumericChangeId;
+      element.repo = 'test-project' as RepoName;
+      element.patchSet = 1 as RevisionPatchSetNum;
+
+      element.fixSuggestionInfo = {
+        ...createFixSuggestionInfo(),
+        replacements: [
+          {
+            path: 'foo/bar.ts',
+            replacement: 'new content',
+            range: createRange(),
+          },
+        ],
+      };
+    });
+
+    test('navigates to createDiffUrl when in Diff View', async () => {
+      testResolver(changeViewModelToken).setState(createDiffViewState());
+
+      await element.applyFix();
+
+      assert.isTrue(setUrlStub.calledOnce);
+      assert.equal(
+        setUrlStub.lastCall.firstArg,
+        '/c/test-project/+/42/1..edit/foo/bar.ts?forceReload=true'
+      );
+    });
+
+    test('navigates to createChangeUrl when in Change View', async () => {
+      testResolver(changeViewModelToken).setState(createChangeViewState());
+
+      await element.applyFix();
+
+      assert.isTrue(setUrlStub.calledOnce);
+      assert.equal(
+        setUrlStub.lastCall.firstArg,
+        '/c/test-project/+/42/1..edit?forceReload=true'
+      );
+    });
+  });
 });
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 03c944f..0ee5e99 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -253,6 +253,9 @@
   ) {
     params.push(`checksPatchset=${state.checksPatchset}`);
   }
+  if (state.forceReload) {
+    params.push('forceReload=true');
+  }
   if (params.length > 0) {
     queryParams = '?' + params.join('&');
   }
@@ -285,6 +288,26 @@
   return `${createChangeUrlCommon(state)}${path},edit${suffix}`;
 }
 
+export function createApplyFixUrl(
+  obj: (CreateChangeUrlObject | Omit<ChangeViewState, 'view' | 'childView'>) & {
+    filePath?: string;
+    currentChildView?: ChangeChildView;
+  }
+): string {
+  const {filePath, currentChildView, ...restObj} = obj;
+  if (currentChildView === ChangeChildView.DIFF && filePath) {
+    return createDiffUrl({
+      ...restObj,
+      patchNum: EDIT,
+      diffView: {path: filePath},
+    });
+  }
+  return createChangeUrl({
+    ...restObj,
+    patchNum: EDIT,
+  });
+}
+
 /**
  * The shared part of creating a change URL between OVERVIEW, DIFF and EDIT
  * child views.
diff --git a/polygerrit-ui/app/models/views/change_test.ts b/polygerrit-ui/app/models/views/change_test.ts
index e05adb6..8bebe21 100644
--- a/polygerrit-ui/app/models/views/change_test.ts
+++ b/polygerrit-ui/app/models/views/change_test.ts
@@ -6,10 +6,13 @@
 import {assert} from '@open-wc/testing';
 import {
   BasePatchSetNum,
+  EDIT,
+  NumericChangeId,
   PatchSetNumber,
   RepoName,
   RevisionPatchSetNum,
 } from '../../api/rest-api';
+
 import '../../test/common-test-setup';
 import {
   createChangeViewState,
@@ -17,7 +20,9 @@
   createEditViewState,
 } from '../../test/test-data-generators';
 import {
+  ChangeChildView,
   ChangeViewState,
+  createApplyFixUrl,
   createChangeUrl,
   createDiffUrl,
   createEditUrl,
@@ -114,6 +119,14 @@
       );
     });
 
+    test('forceReload', () => {
+      params.forceReload = true;
+      assert.equal(
+        createDiffUrl(params),
+        '/c/test-project/+/42/12/x%252By/path.cpp?forceReload=true'
+      );
+    });
+
     test('base patchset', () => {
       params.basePatchNum = 6 as BasePatchSetNum;
       assert.equal(
@@ -186,4 +199,36 @@
     assert.equal(createEditUrl(params).substring(0, 5), '/base');
     window.CANONICAL_PATH = undefined;
   });
+
+  suite('createApplyFixUrl', () => {
+    test('Diff View context', () => {
+      assert.equal(
+        createApplyFixUrl({
+          changeNum: 42 as NumericChangeId,
+          repo: 'test-project' as RepoName,
+          patchNum: EDIT,
+          basePatchNum: 1 as BasePatchSetNum,
+          forceReload: true,
+          filePath: 'foo/bar.ts',
+          currentChildView: ChangeChildView.DIFF,
+        }),
+        '/c/test-project/+/42/1..edit/foo/bar.ts?forceReload=true'
+      );
+    });
+
+    test('Overview context', () => {
+      assert.equal(
+        createApplyFixUrl({
+          changeNum: 42 as NumericChangeId,
+          repo: 'test-project' as RepoName,
+          patchNum: EDIT,
+          basePatchNum: 1 as BasePatchSetNum,
+          forceReload: true,
+          filePath: 'foo/bar.ts',
+          currentChildView: ChangeChildView.OVERVIEW,
+        }),
+        '/c/test-project/+/42/1..edit?forceReload=true'
+      );
+    });
+  });
 });
diff --git a/resources/com/google/gerrit/server/mime/mime-types.properties b/resources/com/google/gerrit/server/mime/mime-types.properties
index 66f21b9..f557cfd 100644
--- a/resources/com/google/gerrit/server/mime/mime-types.properties
+++ b/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -244,6 +244,7 @@
 svg = application/xml
 svh = text/x-systemverilog
 swift = text/x-swift
+tada = text/x-python
 tcl = text/x-tcl
 tex = text/x-latex
 text = text/plain