Merge "Move ApplyPatchUtil.java to a different package."
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSet.java b/java/com/google/gerrit/extensions/registration/DynamicSet.java
index 9925a66..0c2691a 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -122,7 +122,7 @@
*/
public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type, Named name) {
binder.disableCircularProxies();
- return bind(binder, TypeLiteral.get(type));
+ return bind(binder, TypeLiteral.get(type), name);
}
/**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index 4814733..29d2bf0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -127,6 +127,7 @@
'line-selected': CustomEvent<LineSelectedEventDetail>;
// Fired if being logged in is required.
'show-auth-required': CustomEvent<{}>;
+ 'reload-diff': CustomEvent<{path: string | undefined}>;
}
}
@@ -343,6 +344,11 @@
this.addEventListener('diff-context-expanded', event =>
this.handleDiffContextExpanded(event)
);
+ this.addEventListener('reload-diff', (e: CustomEvent) => {
+ if (e.detail.path === this.path) {
+ this.reload(false);
+ }
+ });
subscribe(
this,
() => this.getBrowserModel().diffViewMode$,
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-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index d183211..6e76aaa 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -305,11 +305,14 @@
private convertCodeToSuggestions() {
const marks = this.renderRoot.querySelectorAll('mark');
- for (const userSuggestionMark of marks) {
+ marks.forEach((userSuggestionMark, index) => {
const userSuggestion = document.createElement('gr-user-suggestion-fix');
// Temporary workaround for bug - tabs replacement
if (this.content.includes('\t')) {
- userSuggestion.textContent = getUserSuggestionFromString(this.content);
+ userSuggestion.textContent = getUserSuggestionFromString(
+ this.content,
+ index
+ );
} else {
userSuggestion.textContent = userSuggestionMark.textContent ?? '';
}
@@ -317,7 +320,7 @@
userSuggestion,
userSuggestionMark
);
- }
+ });
}
}
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..d059c7c 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);
+ return 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, {
@@ -353,6 +369,7 @@
forceReload: !this.hasEdit,
})
);
+ fire(this, 'reload-diff', {path: this.comment?.path});
fire(this, 'apply-user-suggestion', {});
}
}
diff --git a/polygerrit-ui/app/models/checks/checks-util.ts b/polygerrit-ui/app/models/checks/checks-util.ts
index 85a7be4..5eb9cac 100644
--- a/polygerrit-ui/app/models/checks/checks-util.ts
+++ b/polygerrit-ui/app/models/checks/checks-util.ts
@@ -147,7 +147,9 @@
if (replacements.length === 0) return undefined;
return {
- description: fix.description || `Fix provided by ${checkName}`,
+ description: [fix.description, `Fix provided by ${checkName}`]
+ .filter(Boolean)
+ .join(' - '),
fix_id: PROVIDED_FIX_ID,
replacements,
};
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index c8e5173..1324061 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -556,12 +556,17 @@
return comment.message?.includes(USER_SUGGESTION_START_PATTERN) ?? false;
}
-export function getUserSuggestionFromString(content: string) {
- const start =
- content.indexOf(USER_SUGGESTION_START_PATTERN) +
- USER_SUGGESTION_START_PATTERN.length;
- const end = content.indexOf('\n```', start);
- return content.substring(start, end);
+export function getUserSuggestionFromString(
+ content: string,
+ suggestionIndex = 0
+) {
+ const suggestions = content.split(USER_SUGGESTION_START_PATTERN).slice(1);
+ if (suggestions.length === 0) return '';
+
+ const targetIndex = Math.min(suggestionIndex, suggestions.length - 1);
+ const targetSuggestion = suggestions[targetIndex];
+ const end = targetSuggestion.indexOf('\n```');
+ return end !== -1 ? targetSuggestion.substring(0, end) : targetSuggestion;
}
export function getUserSuggestion(comment: Comment) {
diff --git a/polygerrit-ui/app/utils/comment-util_test.ts b/polygerrit-ui/app/utils/comment-util_test.ts
index 16c8bfd..031a738 100644
--- a/polygerrit-ui/app/utils/comment-util_test.ts
+++ b/polygerrit-ui/app/utils/comment-util_test.ts
@@ -18,6 +18,7 @@
getMentionedThreads,
isNewThread,
createNew,
+ getUserSuggestionFromString,
} from './comment-util';
import {
createAccountWithEmail,
@@ -533,4 +534,69 @@
]);
});
});
+
+ suite('getUserSuggestionFromString', () => {
+ const createSuggestionContent = (suggestions: string[]) =>
+ suggestions
+ .map(s => `${USER_SUGGESTION_START_PATTERN}${s}\n\`\`\``)
+ .join('\n');
+
+ test('returns empty string for content without suggestions', () => {
+ const content = 'This is a comment without any suggestions.';
+ assert.equal(getUserSuggestionFromString(content), '');
+ });
+
+ test('returns first suggestion when no index is provided', () => {
+ const content = createSuggestionContent(['First suggestion']);
+ assert.equal(getUserSuggestionFromString(content), 'First suggestion');
+ });
+
+ test('returns correct suggestion for given index', () => {
+ const content = createSuggestionContent([
+ 'First suggestion',
+ 'Second suggestion',
+ 'Third suggestion',
+ ]);
+ assert.equal(
+ getUserSuggestionFromString(content, 1),
+ 'Second suggestion'
+ );
+ });
+
+ test('returns last suggestion when index is out of bounds', () => {
+ const content = createSuggestionContent([
+ 'First suggestion',
+ 'Second suggestion',
+ ]);
+ assert.equal(
+ getUserSuggestionFromString(content, 5),
+ 'Second suggestion'
+ );
+ });
+
+ test('handles suggestion without closing backticks', () => {
+ const content = `${USER_SUGGESTION_START_PATTERN}Unclosed suggestion`;
+ assert.equal(getUserSuggestionFromString(content), 'Unclosed suggestion');
+ });
+
+ test('handles multiple suggestions with varying content', () => {
+ const content = createSuggestionContent([
+ 'First\nMultiline\nSuggestion',
+ 'Second suggestion',
+ 'Third suggestion with `backticks`',
+ ]);
+ assert.equal(
+ getUserSuggestionFromString(content, 0),
+ 'First\nMultiline\nSuggestion'
+ );
+ assert.equal(
+ getUserSuggestionFromString(content, 1),
+ 'Second suggestion'
+ );
+ assert.equal(
+ getUserSuggestionFromString(content, 2),
+ 'Third suggestion with `backticks`'
+ );
+ });
+ });
});