Merge branch 'main' into lunny/refactor_clone_push

pull/36093/head
Giteabot 2025-12-10 18:46:09 +07:00 committed by GitHub
commit 20d1c6d19d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 69 additions and 15 deletions

@ -337,14 +337,14 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
if rootCfg.Section(oldSection).HasKey(oldKey) { if rootCfg.Section(oldSection).HasKey(oldKey) {
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
} }
} }
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini // deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
if rootCfg.Section(oldSection).HasKey(oldKey) { if rootCfg.Section(oldSection).HasKey(oldKey) {
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey) LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
} }
} }

@ -118,7 +118,7 @@ func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
if uidChanged { if uidChanged {
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id) _ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
} else if cookieToken != "" { } else if cookieToken != "" {
// If cookie token presents, re-use existing unexpired token, else generate a new one. // If cookie token present, re-use existing unexpired token, else generate a new one.
if issueTime, ok := ParseCsrfToken(cookieToken); ok { if issueTime, ok := ParseCsrfToken(cookieToken); ok {
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time. dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval { if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {

@ -16,6 +16,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system" system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/hostmatcher"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -327,6 +328,9 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
messenger("repo.migrate.migrating_issues") messenger("repo.migrate.migrating_issues")
issueBatchSize := uploader.MaxBatchInsertSize("issue") issueBatchSize := uploader.MaxBatchInsertSize("issue")
// because when the migrating is running, some issues maybe removed, so after the next page
// some of issue maybe duplicated, so we need to record the inserted issue indexes
mapInsertedIssueIndexes := container.Set[int64]{}
for i := 1; ; i++ { for i := 1; ; i++ {
issues, isEnd, err := downloader.GetIssues(ctx, i, issueBatchSize) issues, isEnd, err := downloader.GetIssues(ctx, i, issueBatchSize)
if err != nil { if err != nil {
@ -336,6 +340,14 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
log.Warn("migrating issues is not supported, ignored") log.Warn("migrating issues is not supported, ignored")
break break
} }
for i := 0; i < len(issues); i++ {
if mapInsertedIssueIndexes.Contains(issues[i].Number) {
issues = append(issues[:i], issues[i+1:]...)
i--
continue
}
mapInsertedIssueIndexes.Add(issues[i].Number)
}
if err := uploader.CreateIssues(ctx, issues...); err != nil { if err := uploader.CreateIssues(ctx, issues...); err != nil {
return err return err
@ -381,6 +393,7 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
log.Trace("migrating pull requests and comments") log.Trace("migrating pull requests and comments")
messenger("repo.migrate.migrating_pulls") messenger("repo.migrate.migrating_pulls")
prBatchSize := uploader.MaxBatchInsertSize("pullrequest") prBatchSize := uploader.MaxBatchInsertSize("pullrequest")
mapInsertedPRIndexes := container.Set[int64]{}
for i := 1; ; i++ { for i := 1; ; i++ {
prs, isEnd, err := downloader.GetPullRequests(ctx, i, prBatchSize) prs, isEnd, err := downloader.GetPullRequests(ctx, i, prBatchSize)
if err != nil { if err != nil {
@ -390,6 +403,14 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
log.Warn("migrating pull requests is not supported, ignored") log.Warn("migrating pull requests is not supported, ignored")
break break
} }
for i := 0; i < len(prs); i++ {
if mapInsertedPRIndexes.Contains(prs[i].Number) {
prs = append(prs[:i], prs[i+1:]...)
i--
continue
}
mapInsertedPRIndexes.Add(prs[i].Number)
}
if err := uploader.CreatePullRequests(ctx, prs...); err != nil { if err := uploader.CreatePullRequests(ctx, prs...); err != nil {
return err return err

@ -17,7 +17,7 @@ import {POST} from '../../modules/fetch.ts';
import { import {
EventEditorContentChanged, EventEditorContentChanged,
initTextareaMarkdown, initTextareaMarkdown,
textareaInsertText, replaceTextareaSelection,
triggerEditorContentChanged, triggerEditorContentChanged,
} from './EditorMarkdown.ts'; } from './EditorMarkdown.ts';
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts'; import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
@ -273,7 +273,7 @@ export class ComboMarkdownEditor {
let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]')!.value); let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]')!.value);
rows = Math.max(1, Math.min(100, rows)); rows = Math.max(1, Math.min(100, rows));
cols = Math.max(1, Math.min(100, cols)); cols = Math.max(1, Math.min(100, cols));
textareaInsertText(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`); replaceTextareaSelection(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`);
addTablePanelTippy.hide(); addTablePanelTippy.hide();
}); });
} }

@ -4,15 +4,24 @@ export function triggerEditorContentChanged(target: HTMLElement) {
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true})); target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
} }
export function textareaInsertText(textarea: HTMLTextAreaElement, value: string) { /** replace selected text or insert text by creating a new edit history entry,
const startPos = textarea.selectionStart; * e.g. CTRL-Z works after this */
const endPos = textarea.selectionEnd; export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) {
textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos); const before = textarea.value.slice(0, textarea.selectionStart);
textarea.selectionStart = startPos; const after = textarea.value.slice(textarea.selectionEnd);
textarea.selectionEnd = startPos + value.length;
textarea.focus(); textarea.focus();
let success = false;
try {
success = document.execCommand('insertText', false, text); // eslint-disable-line @typescript-eslint/no-deprecated
} catch {}
// fall back to regular replacement
if (!success) {
textarea.value = `${before}${text}${after}`;
triggerEditorContentChanged(textarea); triggerEditorContentChanged(textarea);
} }
}
type TextareaValueSelection = { type TextareaValueSelection = {
value: string; value: string;
@ -176,7 +185,7 @@ export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHa
return {handled: true, valueSelection: {value: linesBuf.lines.join('\n'), selStart: newPos, selEnd: newPos}}; return {handled: true, valueSelection: {value: linesBuf.lines.join('\n'), selStart: newPos, selEnd: newPos}};
} }
function handleNewline(textarea: HTMLTextAreaElement, e: Event) { function handleNewline(textarea: HTMLTextAreaElement, e: KeyboardEvent) {
const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd}); const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd});
if (!ret.handled || !ret.valueSelection) return; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled) if (!ret.handled || !ret.valueSelection) return; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled)
e.preventDefault(); e.preventDefault();
@ -185,6 +194,28 @@ function handleNewline(textarea: HTMLTextAreaElement, e: Event) {
triggerEditorContentChanged(textarea); triggerEditorContentChanged(textarea);
} }
// Keys that act as dead keys will not work because the spec dictates that such keys are
// emitted as `Dead` in e.key instead of the actual key.
const pairs = new Map<string, string>([
["'", "'"],
['"', '"'],
['`', '`'],
['(', ')'],
['[', ']'],
['{', '}'],
['<', '>'],
]);
function handlePairCharacter(textarea: HTMLTextAreaElement, e: KeyboardEvent): void {
const selStart = textarea.selectionStart;
const selEnd = textarea.selectionEnd;
if (selEnd === selStart) return; // do not process when no selection
e.preventDefault();
const inner = textarea.value.substring(selStart, selEnd);
replaceTextareaSelection(textarea, `${e.key}${inner}${pairs.get(e.key)}`);
textarea.setSelectionRange(selStart + 1, selEnd + 1);
}
function isTextExpanderShown(textarea: HTMLElement): boolean { function isTextExpanderShown(textarea: HTMLElement): boolean {
return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions')); return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions'));
} }
@ -198,6 +229,8 @@ export function initTextareaMarkdown(textarea: HTMLTextAreaElement) {
} else if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) { } else if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
// use Enter to insert a new line with the same indention and prefix // use Enter to insert a new line with the same indention and prefix
handleNewline(textarea, e); handleNewline(textarea, e);
} else if (pairs.has(e.key)) {
handlePairCharacter(textarea, e);
} }
}); });
} }

@ -1,5 +1,5 @@
import {imageInfo} from '../../utils/image.ts'; import {imageInfo} from '../../utils/image.ts';
import {textareaInsertText, triggerEditorContentChanged} from './EditorMarkdown.ts'; import {replaceTextareaSelection, triggerEditorContentChanged} from './EditorMarkdown.ts';
import { import {
DropzoneCustomEventRemovedFile, DropzoneCustomEventRemovedFile,
DropzoneCustomEventUploadDone, DropzoneCustomEventUploadDone,
@ -43,7 +43,7 @@ class TextareaEditor {
} }
insertPlaceholder(value: string) { insertPlaceholder(value: string) {
textareaInsertText(this.editor, value); replaceTextareaSelection(this.editor, value);
} }
replacePlaceholder(oldVal: string, newVal: string) { replacePlaceholder(oldVal: string, newVal: string) {