From a5b22fa512db3934666d6ed6e0742fe145c9e628 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:26:36 +0800 Subject: [PATCH] fix(webdav): allow clearing saved configuration Close #8495 --- Changelog.md | 1 + scripts-workflow/release.mjs | 83 +++++++++++++------ .../setting/mods/backup-config-viewer.tsx | 31 +++---- src/services/webdav-status.ts | 6 +- 4 files changed, 81 insertions(+), 40 deletions(-) diff --git a/Changelog.md b/Changelog.md index bdf056092..7f4578cec 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,7 @@ - 修复 代理 页展开分组后不显示最新测速结果,需切换节点才刷新 - 修复 代理 在全局模式下无法展开节点组 - 修复 自定义 CSS 无法修改编辑器字体和字号的问题 +- 修复 WebDAV 设置清空后无法保存 **🍎 macOS** diff --git a/scripts-workflow/release.mjs b/scripts-workflow/release.mjs index ed0a63c0d..dc0083fb2 100644 --- a/scripts-workflow/release.mjs +++ b/scripts-workflow/release.mjs @@ -134,6 +134,31 @@ function fetchBranches() { ) } +function discardClosedCandidate(branch, base, prefix) { + const candidates = list( + `pulls?state=all&base=${base}&head=${repository.split('/')[0]}:${branch}&per_page=100`, + ) + const latest = candidates[0] + if (!latest || candidates.some((pr) => pr.state === 'open')) + throw new Error( + `Branch ${branch} is reserved by an active or unknown candidate.`, + ) + const pr = get(`pulls/${latest.number}`) + requireBotPr(pr, base, prefix) + if (pr.state !== 'closed' || pr.merged) + throw new Error( + `Close the unmerged candidate before retrying: ${pr.html_url}`, + ) + updateRefs(pr.base.repo.node_id, [ + { + name: `refs/heads/${branch}`, + beforeOid: pr.head.sha, + afterOid: '0'.repeat(40), + }, + ]) + summary(`Released the unused branch from closed PR: ${pr.html_url}`) +} + function prepare() { let refresh if (env.GITHUB_EVENT_NAME === 'pull_request_target') { @@ -211,8 +236,15 @@ function prepare() { : last ? `${base}-rc.${BigInt(last.match(rcPattern)[4] ?? 0) + 1n}` : `${base}-rc`) - if (occupied.includes(tag)) - throw new Error(`Tag ${tag} already exists or is reserved.`) + if (tags.includes(tag)) throw new Error(`Tag ${tag} already exists.`) + for (const [prefix, target] of [ + ['release/', 'dev'], + ['publish/', 'main'], + ]) { + const branch = `${prefix}${tag}` + if (refs.includes(`refs/heads/${branch}`) && branch !== refresh?.head.ref) + discardClosedCandidate(branch, target, prefix) + } const sha = git('rev-parse', 'HEAD') const body = releaseBody(tag, sha, tags, false) summary(body) @@ -453,7 +485,7 @@ function publishPr() { validateVersionCommit(tag, sha) const branch = `publish/${tag}` const existing = list( - `pulls?state=all&base=main&head=${repository.split('/')[0]}:${branch}&per_page=100`, + `pulls?state=open&base=main&head=${repository.split('/')[0]}:${branch}&per_page=100`, ) if (existing.length) { const publication = existing[0] @@ -567,28 +599,31 @@ function publish() { requireApproval(pr, sha) const main = git('rev-parse', 'origin/main') git('merge-base', '--is-ancestor', main, sha) - const object = api('git/tags', { + git( + '-c', + 'user.name=github-actions[bot]', + '-c', + 'user.email=41898282+github-actions[bot]@users.noreply.github.com', + 'tag', + '--annotate', + '--no-sign', + '--message', + marker, tag, - message: marker, - object: sha, - type: 'commit', - }) - // The tag records run ownership in the same transaction as the fast-forward. - updateRefs(pr.base.repo.node_id, [ - { name: 'refs/heads/main', beforeOid: main, afterOid: sha, force: false }, - { - name: `refs/heads/${pr.head.ref}`, - beforeOid: sha, - afterOid: sha, - force: false, - }, - { - name: `refs/tags/${tag}`, - beforeOid: '0'.repeat(40), - afterOid: object.sha, - force: false, - }, - ]) + sha, + ) + // GraphQL updateRefs rejects annotated tag objects; Git preserves atomicity. + git( + 'push', + '--atomic', + `--force-with-lease=refs/heads/main:${main}`, + `--force-with-lease=refs/heads/${pr.head.ref}:${sha}`, + `--force-with-lease=refs/tags/${tag}:`, + 'origin', + `${sha}:refs/heads/main`, + `${sha}:refs/heads/${pr.head.ref}`, + `refs/tags/${tag}:refs/tags/${tag}`, + ) summary(`Fast-forwarded main and tagged ${tag} at approved commit ${sha}.`) } releaseOutput(tag, sha) diff --git a/src/components/setting/mods/backup-config-viewer.tsx b/src/components/setting/mods/backup-config-viewer.tsx index c0ce70c10..84b7d579a 100644 --- a/src/components/setting/mods/backup-config-viewer.tsx +++ b/src/components/setting/mods/backup-config-viewer.tsx @@ -40,9 +40,9 @@ export const BackupConfigViewer = memo( const { register, handleSubmit, watch } = useForm({ defaultValues: { - url: webdav_url!, - username: webdav_username!, - password: webdav_password!, + url: webdav_url ?? '', + username: webdav_username ?? '', + password: webdav_password ?? '', }, }) const url = watch('url') @@ -50,9 +50,9 @@ export const BackupConfigViewer = memo( const password = watch('password') const webdavChanged = - webdav_url !== url || - webdav_username !== username || - webdav_password !== password + (webdav_url ?? '') !== url || + (webdav_username ?? '') !== username || + (webdav_password ?? '') !== password const webdavSignature = buildWebdavSignature(verge) const webdavStatus = getWebdavStatus(webdavSignature) @@ -69,9 +69,9 @@ export const BackupConfigViewer = memo( }, [webdav_url, webdav_username, webdav_password, onInit, shouldAutoInit]) const checkForm = () => { - const username = usernameRef.current?.value + const username = usernameRef.current?.value.trim() const password = passwordRef.current?.value - const url = urlRef.current?.value + const url = urlRef.current?.value.trim() if (!url) { urlRef.current?.focus() @@ -95,14 +95,15 @@ export const BackupConfigViewer = memo( } const save = useLockFn(async (data: IWebDavConfig) => { - checkForm() + const trimmedUrl = data.url.trim() + const trimmedUsername = data.username.trim() + const isClearing = !trimmedUrl && !trimmedUsername && !data.password + if (!isClearing) checkForm() const signature = buildWebdavSignature({ webdav_url: data.url, webdav_username: data.username, webdav_password: data.password, }) - const trimmedUrl = data.url.trim() - const trimmedUsername = data.username.trim() try { setLoading(true) @@ -127,7 +128,7 @@ export const BackupConfigViewer = memo( ) setWebdavStatus(signature, 'unknown') showNotice.success('settings.modals.backup.messages.webdavConfigSaved') - await onSaveSuccess(signature) + if (!isClearing) await onSaveSuccess(signature) } catch (error) { showNotice.error( 'settings.modals.backup.messages.webdavConfigSaveFailed', @@ -223,9 +224,9 @@ export const BackupConfigViewer = memo( }} > {webdavChanged || - webdav_url === undefined || - webdav_username === undefined || - webdav_password === undefined ? ( + !webdav_url || + !webdav_username || + !webdav_password ? (