Merge "Enable apply fixes from old patchset to be applied on latest"
diff --git a/polygerrit-ui/app/elements/shared/gr-fix-suggestions/gr-fix-suggestions.ts b/polygerrit-ui/app/elements/shared/gr-fix-suggestions/gr-fix-suggestions.ts
index ca7fe9c..36266a0 100644
--- a/polygerrit-ui/app/elements/shared/gr-fix-suggestions/gr-fix-suggestions.ts
+++ b/polygerrit-ui/app/elements/shared/gr-fix-suggestions/gr-fix-suggestions.ts
@@ -7,7 +7,7 @@
 import '../../shared/gr-icon/gr-icon';
 import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
 import '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
-import {css, html, LitElement} from 'lit';
+import {css, html, LitElement, PropertyValues} from 'lit';
 import {customElement, state, query, property} from 'lit/decorators.js';
 import {fire} from '../../../utils/event-util';
 import {getDocUrl} from '../../../utils/url-util';
@@ -16,7 +16,7 @@
 import {configModelToken} from '../../../models/config/config-model';
 import {GrSuggestionDiffPreview} from '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
 import {changeModelToken} from '../../../models/change/change-model';
-import {Comment, PatchSetNumber} from '../../../types/common';
+import {Comment, NumericChangeId, PatchSetNumber} from '../../../types/common';
 import {OpenFixPreviewEventDetail} from '../../../types/events';
 import {pluginLoaderToken} from '../gr-js-api-interface/gr-plugin-loader';
 import {SuggestionsProvider} from '../../../api/suggestions';
@@ -25,6 +25,7 @@
 import {storageServiceToken} from '../../../services/storage/gr-storage_impl';
 import {getAppContext} from '../../../services/app-context';
 import {Interaction} from '../../../constants/reporting';
+import {isFileUnchanged} from '../../../utils/diff-util';
 
 export const COLLAPSE_SUGGESTION_STORAGE_KEY = 'collapseSuggestionStorageKey';
 
@@ -47,11 +48,15 @@
 
   @state() latestPatchNum?: PatchSetNumber;
 
+  @state() changeNum?: NumericChangeId;
+
   @state()
   suggestionsProvider?: SuggestionsProvider;
 
   @state() private isOwner = false;
 
+  @state() private enableApplyOnUnModifiedFile = false;
+
   /**
    * This is just a reflected property such that css rules can be based on it.
    */
@@ -68,6 +73,8 @@
 
   private readonly reporting = getAppContext().reportingService;
 
+  private readonly restApiService = getAppContext().restApiService;
+
   constructor() {
     super();
     subscribe(
@@ -85,6 +92,17 @@
       () => this.getChangeModel().isOwner$,
       x => (this.isOwner = x)
     );
+    subscribe(
+      this,
+      () => this.getChangeModel().changeNum$,
+      x => (this.changeNum = x)
+    );
+  }
+
+  override updated(changed: PropertyValues) {
+    if (changed.has('changeNum') || changed.has('latestPatchNum')) {
+      this.checkIfcanEnableApplyOnUnModifiedFile();
+    }
   }
 
   override connectedCallback() {
@@ -277,7 +295,9 @@
     if (!this.comment?.fix_suggestions) return;
     this.applyingFix = true;
     try {
-      await this.suggestionDiffPreview?.applyFixSuggestion();
+      await this.suggestionDiffPreview?.applyFixSuggestion(
+        this.enableApplyOnUnModifiedFile
+      );
     } finally {
       this.applyingFix = false;
     }
@@ -285,6 +305,7 @@
 
   private isApplyEditDisabled() {
     if (this.comment?.patch_set === undefined) return true;
+    if (this.enableApplyOnUnModifiedFile) return false;
     return this.comment.patch_set !== this.latestPatchNum;
   }
 
@@ -294,6 +315,29 @@
       ? 'You cannot apply this fix because it is from a previous patchset'
       : '';
   }
+
+  private async checkIfcanEnableApplyOnUnModifiedFile() {
+    // if enabled we don't need to enable
+    if (!this.isApplyEditDisabled()) return;
+
+    const basePatchNum = this.comment?.patch_set;
+    const path = this.comment?.path;
+
+    if (!basePatchNum || !this.latestPatchNum || !path || !this.changeNum) {
+      return;
+    }
+
+    const diff = await this.restApiService.getDiff(
+      this.changeNum,
+      basePatchNum,
+      this.latestPatchNum,
+      path
+    );
+
+    if (diff && isFileUnchanged(diff)) {
+      this.enableApplyOnUnModifiedFile = true;
+    }
+  }
 }
 
 declare global {
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 87c81c0..b9984a9 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
@@ -7,7 +7,13 @@
 import {css, html, LitElement, nothing, PropertyValues} from 'lit';
 import {customElement, property, state} from 'lit/decorators.js';
 import {getAppContext} from '../../../services/app-context';
-import {Comment, EDIT, BasePatchSetNum, RepoName} from '../../../types/common';
+import {
+  Comment,
+  EDIT,
+  BasePatchSetNum,
+  PatchSetNumber,
+  RepoName,
+} from '../../../types/common';
 import {anyLineTooLong} from '../../../utils/diff-util';
 import {
   DiffLayer,
@@ -79,6 +85,8 @@
   @state()
   diffPrefs?: DiffPreferencesInfo;
 
+  @state() latestPatchNum?: PatchSetNumber;
+
   @state()
   renderPrefs: RenderPreferences = {
     disable_context_control_buttons: true,
@@ -120,6 +128,11 @@
     );
     subscribe(
       this,
+      () => this.getChangeModel().latestPatchNum$,
+      x => (this.latestPatchNum = x)
+    );
+    subscribe(
+      this,
       () => this.getUserModel().diffPreferences$,
       diffPreferences => {
         if (!diffPreferences) return;
@@ -300,9 +313,9 @@
    * Similar code flow is in gr-apply-fix-dialog.handleApplyFix
    * Used in gr-fix-suggestions
    */
-  public applyFixSuggestion() {
+  public applyFixSuggestion(onLatestPatchset = false) {
     if (this.suggestion || !this.fixSuggestionInfo) return;
-    this.applyFix(this.fixSuggestionInfo);
+    this.applyFix(this.fixSuggestionInfo, onLatestPatchset);
   }
 
   /**
@@ -324,7 +337,10 @@
     this.applyFix(fixSuggestions[0]);
   }
 
-  private async applyFix(fixSuggestion: FixSuggestionInfo) {
+  private async applyFix(
+    fixSuggestion: FixSuggestionInfo,
+    onLatestPatchset = false
+  ) {
     const changeNum = this.changeNum;
     const basePatchNum = this.comment?.patch_set as BasePatchSetNum;
     if (!changeNum || !basePatchNum || !fixSuggestion) return;
@@ -332,7 +348,7 @@
     this.reporting.time(Timing.APPLY_FIX_LOAD);
     const res = await this.restApiService.applyFixSuggestion(
       changeNum,
-      basePatchNum,
+      onLatestPatchset ? this.latestPatchNum ?? basePatchNum : basePatchNum,
       fixSuggestion.replacements
     );
     this.reporting.timeEnd(Timing.APPLY_FIX_LOAD, {