feat(git-sync): app-aware pull defaults, always webhook delivery, token-repo guidance

- Pull-from-Git defaults on only for app-backed repos (applied when the
  selected resource resolves); polling is opt-in for token repositories,
  with a warning alert recommending the GitHub App (instant pull + in-app
  PRs) or the sync GitHub workflow
- App repos always use webhook delivery with polling fallback: the delivery
  selector is gone and a stored polling mode is normalized back to auto
- Post-save modal reflects the auto-pull state instead of telling the user
  to turn on a toggle that is already on
- Non-app PR hints recommend the GitHub App explicitly

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PP5gBSPfo1YtkL1sWVAjJm
This commit is contained in:
hugocasa
2026-07-09 13:47:44 +02:00
co-authored by Claude Fable 5
parent fdce2d40d3
commit 5ee9ca9b23
4 changed files with 82 additions and 67 deletions
@@ -1,5 +1,5 @@
import { getContext, setContext } from 'svelte'
import { enterpriseLicense, userWorkspaces } from '$lib/stores'
import { enterpriseLicense } from '$lib/stores'
import { get } from 'svelte/store'
import { sendUserToast } from '$lib/toast'
import { JobService, WorkspaceService, ResourceService } from '$lib/gen'
@@ -45,7 +45,7 @@ export type GitSyncSettings = {
export type ModalState = {
push: { idx: number; repo: GitSyncRepository; open: boolean } | null
pull: { idx: number; repo: GitSyncRepository; open: boolean; settingsOnly?: boolean } | null
success: { open: boolean; savedWithoutInit?: boolean } | null
success: { open: boolean; savedWithoutInit?: boolean; autoPullOn?: boolean } | null
}
export type ValidationState = {
@@ -282,8 +282,8 @@ export function createGitSyncContext(workspace: string) {
closeModal('pull')
}
function showSuccessModal(savedWithoutInit?: boolean) {
activeModals.success = { open: true, savedWithoutInit }
function showSuccessModal(savedWithoutInit?: boolean, autoPullOn?: boolean) {
activeModals.success = { open: true, savedWithoutInit, autoPullOn }
}
function closeSuccessModal() {
@@ -528,7 +528,7 @@ export function createGitSyncContext(workspace: string) {
repoToSave.detectionState = undefined
repoToSave.extractedSettings = undefined
// Show success modal for new connections
showSuccessModal(savedWithoutInit)
showSuccessModal(savedWithoutInit, repoToSave.auto_pull?.enabled === true)
}
}
@@ -664,14 +664,6 @@ export function createGitSyncContext(workspace: string) {
}
}
// Mirrors the card's fork detection: wm-fork- id prefix, or a parent
// workspace id (dev workspaces are prefix-less).
function isForkOrDevWorkspace(): boolean {
if (workspace.startsWith('wm-fork-')) return true
const ws = get(userWorkspaces)?.find((w) => w.id === workspace)
return !!ws?.parent_workspace_id
}
function addSyncRepository() {
if (!get(enterpriseLicense) && repositories && repositories.length >= 1) {
sendUserToast('Multiple repositories requires Enterprise Edition', true)
@@ -691,19 +683,10 @@ export function createGitSyncContext(workspace: string) {
exclude_types_override: [],
legacyImported: false,
isUnsavedConnection: true,
collapsed: false,
// New connections default to pulling changes from Git (webhook with a
// polling fallback), forks included. Existing repos load without
// auto_pull and stay off. Auto-pull is EE-only (the backend rejects an
// enabled setting on CE) and parent-managed on fork/dev workspaces
// (the backend rejects it there too), so only default it on when
// licensed and not a fork.
...(get(enterpriseLicense) && !isForkOrDevWorkspace()
? {
auto_pull: { enabled: true, mode: 'auto', sync_forks: true },
fork_open_prs: true
}
: {})
collapsed: false
// Pull-from-Git defaults are applied by the repository card once the
// selected resource resolves: only app-backed repos (instant webhook
// delivery) default to auto-pull on; polling is opt-in for token repos.
})
gitSyncTestJobs.push({
jobId: '',
@@ -104,5 +104,6 @@
<GitSyncSuccessModal
bind:open={gitSyncContext.activeModals.success.open}
savedWithoutInit={gitSyncContext.activeModals.success.savedWithoutInit}
autoPullOn={gitSyncContext.activeModals.success.autoPullOn}
/>
{/if}
@@ -22,7 +22,6 @@
import GitSyncModeDisplay from './GitSyncModeDisplay.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import EEOnly from '$lib/components/EEOnly.svelte'
import Select from '$lib/components/select/Select.svelte'
import { ResourceService, VariableService } from '$lib/gen'
let {
@@ -69,15 +68,6 @@
}
}
// Delivery mode shown in the selector. The backend also has a webhook-only
// mode; it's surfaced as "auto" (webhook with polling fallback) here.
function deliveryMode(): 'auto' | 'polling' {
return repo?.auto_pull?.mode === 'polling' ? 'polling' : 'auto'
}
function setDeliveryMode(mode: 'auto' | 'polling') {
if (repo?.auto_pull) repo.auto_pull = { ...repo.auto_pull, mode }
}
// Parent-level fork auto-sync (phase 5). Configured on the parent workspace's
// repo and applied to all of its forks, so hide it when the current workspace
// is itself a fork.
@@ -141,7 +131,7 @@
const abortController = new AbortController()
async function loadResourceInfo() {
if (repo?.git_repo_resource_path && !repo.isUnsavedConnection && $workspaceStore) {
if (repo?.git_repo_resource_path && $workspaceStore) {
loadingResourceInfo = true
resourceInfo = null
// Clear stale app state up front so a resource change or a failed
@@ -157,6 +147,25 @@
// Extract git URL from resource value
const value = resource.value as Record<string, any>
isGithubApp = value?.is_github_app === true
// A newly added connection defaults to pulling from Git only when
// the repository is app-backed (instant webhook delivery). Polling
// is opt-in for token repositories. Fork/dev workspaces never get
// the parent-only defaults (the backend rejects them), and both
// features are EE-only.
if (
repo.isUnsavedConnection &&
isGithubApp &&
!isFork &&
$enterpriseLicense &&
repo.auto_pull === undefined
) {
repo.auto_pull = { enabled: true, mode: 'auto', sync_forks: true }
repo.fork_open_prs = repo.fork_open_prs ?? true
}
// Webhook with polling fallback is the only delivery for app repos.
if (isGithubApp && repo.auto_pull?.mode === 'polling') {
repo.auto_pull = { ...repo.auto_pull, mode: 'auto' }
}
let gitUrl = value?.url || value?.git_url
if (gitUrl && typeof gitUrl === 'string') {
@@ -591,12 +600,12 @@
target="_blank"
class="text-blue-500 hover:underline font-mono">open-pr-on-commit</a
>
workflow in the repository (repositories connected through the
workflow in the repository. Recommended: connect the repository through the
<a
href="https://www.windmill.dev/docs/advanced/git_sync"
target="_blank"
class="text-blue-500 hover:underline">GitHub App</a
> can let Windmill open them automatically).
> and Windmill opens them automatically.
</div>
{/if}
{#if repoMode === 'sync' && isFork}
@@ -636,12 +645,12 @@
target="_blank"
class="text-blue-500 hover:underline font-mono">open-pr-on-fork-commit</a
>
workflow in the repository (repositories connected through the
workflow in the repository. Recommended: connect the repository through the
<a
href="https://www.windmill.dev/docs/advanced/git_sync"
target="_blank"
class="text-blue-500 hover:underline">GitHub App</a
> can let Windmill open them automatically).
> and Windmill opens them automatically.
</div>
{/if}
{/if}
@@ -702,7 +711,7 @@
options={{
right: 'Automatically deploy changes from Git',
rightTooltip:
'Windmill deploys new commits from the tracked branch into this workspace. Repositories connected through the GitHub App sync instantly via webhooks; others are checked about every minute.'
'Windmill deploys new commits from the tracked branch into this workspace. Repositories connected through the GitHub App sync instantly via webhooks with a polling fallback; token-based repositories are checked about every minute.'
}}
on:change={(e) => setAutoPullEnabled(e.detail)}
>
@@ -724,22 +733,30 @@
/>
</div>
{/if}
{#if !isGithubApp && !loadingResourceInfo && !repo.auto_pull?.enabled}
<div class="mt-2">
<Alert type="warning" title="Instant pull recommended" size="xs">
Pull for this repository checks the tracked branch about every minute;
longer gaps make drift and merge conflicts more likely. For instant pull,
connect the repository through the
<a
href="https://www.windmill.dev/docs/advanced/git_sync"
target="_blank"
class="text-blue-500 hover:underline">GitHub App</a
>
(which also lets Windmill manage pull requests), or push changes into
Windmill with the
<a
href="https://www.windmill.dev/docs/advanced/deploy_gh_gl"
target="_blank"
class="text-blue-500 hover:underline">sync GitHub workflow</a
>.
</Alert>
</div>
{/if}
{#if repo.auto_pull?.enabled}
{@const viaWebhook = repo.auto_pull?.webhook_id != null}
{#if isGithubApp}
<div class="mt-3 max-w-sm">
<div class="text-2xs font-semibold text-secondary mb-1">Delivery</div>
<Select
items={[
{ label: 'Webhook with polling fallback', value: 'auto' },
{ label: 'Polling only (air-gapped)', value: 'polling' }
]}
bind:value={() => deliveryMode(), (v) => setDeliveryMode(v)}
clearable={false}
size="sm"
/>
</div>
{:else}
{#if !isGithubApp}
<div class="text-2xs text-secondary mt-2">
Instant webhook sync requires the
<a
@@ -5,9 +5,10 @@
interface Props {
open: boolean
savedWithoutInit?: boolean
autoPullOn?: boolean
}
let { open = $bindable(false), savedWithoutInit = false }: Props = $props()
let { open = $bindable(false), savedWithoutInit = false, autoPullOn = false }: Props = $props()
</script>
<Modal bind:open title="Git Sync Connection Saved" class="sm:max-w-4xl" cancelText="Close">
@@ -37,15 +38,27 @@
{/if}
<!-- Optional setup section -->
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
<h4 class="font-medium text-amber-900 mb-2 flex items-center gap-2">
<ArrowRight class="h-4 w-4" />
Deploy changes from Git back to Windmill
</h4>
<p class="text-sm text-amber-800 mb-3">
Turn on "Automatically deploy changes from Git" on the repository to have Windmill pull new
commits into this workspace for you.
</p>
{#if autoPullOn}
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<h4 class="font-medium text-green-900 mb-2 flex items-center gap-2">
<ArrowRight class="h-4 w-4" />
Pull from Git is on
</h4>
<p class="text-sm text-green-800">
New commits to the tracked branch deploy into this workspace automatically. You can
adjust this anytime on the repository card.
</p>
</div>
{:else}
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
<h4 class="font-medium text-amber-900 mb-2 flex items-center gap-2">
<ArrowRight class="h-4 w-4" />
Deploy changes from Git back to Windmill
</h4>
<p class="text-sm text-amber-800 mb-3">
Turn on "Automatically deploy changes from Git" on the repository to have Windmill pull
new commits into this workspace for you.
</p>
<div class="flex flex-col gap-2">
<p class="text-sm text-amber-700">
Prefer to control deployment from your own pipeline (tests, custom gating, deploy on PR
@@ -60,8 +73,9 @@
<ExternalLink class="h-3 w-3" />
Learn more about CI-based deployment
</a>
</div>
</div>
</div>
</div>
{/if}
</div>
</Modal>