Git LFS section is live with:
Enable LFS toggle — turns LFS on/off for the repo; all other controls dim when disabled File locking toggle — enables the LFS locking protocol for binary assets Maximum file size — optional per-file size cap in MB (blank = unlimited) Info callout linking to the git-lfs client install page and noting the .gitattributes requirement
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
useDefaultDescription, useUpdateDefaultDescription,
|
||||
useExcludedFiles, useUpdateExcludedFiles,
|
||||
} from '../api/queries/prs'
|
||||
import { useLFSSettings, useUpdateLFSSettings } from '../api/queries/lfs'
|
||||
import { useRecentRepos } from '../hooks/useRecentRepos'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { Skeleton } from '../ui/Skeleton'
|
||||
@@ -153,9 +154,10 @@ export default function RepoSettingsPage() {
|
||||
{section === 'default-reviewers' && <DefaultReviewersSection owner={owner} repo={repoName} />}
|
||||
{section === 'default-description' && <DefaultDescriptionSection owner={owner} repo={repoName} />}
|
||||
{section === 'excluded-files' && <ExcludedFilesSection owner={owner} repo={repoName} />}
|
||||
{section === 'git-lfs' && <GitLFSSection owner={owner} repo={repoName} />}
|
||||
{!['repository-details','repository-permissions','access-keys','access-tokens',
|
||||
'branch-restrictions','branching-model','merge-strategies','webhooks',
|
||||
'default-reviewers','default-description','excluded-files'].includes(section) && <ComingSoon sectionId={section} />}
|
||||
'default-reviewers','default-description','excluded-files','git-lfs'].includes(section) && <ComingSoon sectionId={section} />}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
@@ -1680,6 +1682,142 @@ function ExcludedFilesSection({ owner, repo }: { owner: string; repo: string })
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Git LFS ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function GitLFSSection({ owner, repo }: { owner: string; repo: string }) {
|
||||
const { data, isLoading } = useLFSSettings(owner, repo)
|
||||
const update = useUpdateLFSSettings(owner, repo)
|
||||
const [maxSizeInput, setMaxSizeInput] = useState('')
|
||||
const [sizeError, setSizeError] = useState('')
|
||||
const [saved, setSaved] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (data) setMaxSizeInput(data.maxFileSizeMB === 0 ? '' : String(data.maxFileSizeMB))
|
||||
}, [data])
|
||||
|
||||
async function toggle(field: 'enabled' | 'lockingEnabled', value: boolean) {
|
||||
await update.mutateAsync({ [field]: value })
|
||||
}
|
||||
|
||||
async function saveMaxSize(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
setSizeError('')
|
||||
const raw = maxSizeInput.trim()
|
||||
const mb = raw === '' ? 0 : parseInt(raw, 10)
|
||||
if (raw !== '' && (isNaN(mb) || mb < 0)) {
|
||||
setSizeError('Enter a positive number of megabytes, or leave blank for unlimited.')
|
||||
return
|
||||
}
|
||||
await update.mutateAsync({ maxFileSizeMB: mb })
|
||||
setSaved(true)
|
||||
setTimeout(() => setSaved(false), 2000)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="max-w-2xl px-6 py-6 space-y-4">
|
||||
<Skeleton className="h-6 w-48 rounded" />
|
||||
<Skeleton className="h-24 rounded" />
|
||||
<Skeleton className="h-24 rounded" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const enabled = data?.enabled ?? false
|
||||
const lockingEnabled = data?.lockingEnabled ?? true
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl px-6 py-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-[var(--c-text)]">Git LFS</h1>
|
||||
<p className="text-sm text-[var(--c-muted)] mt-1">
|
||||
Git Large File Storage replaces large binary files (images, videos, datasets) with lightweight text pointers inside Git,
|
||||
while storing the actual file content on the server.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Enable / disable */}
|
||||
<div className="border border-[var(--c-border)] rounded-lg divide-y divide-[var(--c-border)]">
|
||||
<div className="flex items-start justify-between gap-4 px-4 py-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--c-text)]">Enable Git LFS</p>
|
||||
<p className="text-xs text-[var(--c-muted)] mt-0.5">
|
||||
Allow contributors to push large files using the LFS protocol.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => toggle('enabled', !enabled)}
|
||||
disabled={update.isPending}
|
||||
className={`relative shrink-0 inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:opacity-50 ${enabled ? 'bg-[var(--c-brand)]' : 'bg-[var(--c-border)]'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 rounded-full bg-white shadow transition-transform ${enabled ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`flex items-start justify-between gap-4 px-4 py-4 transition-opacity ${enabled ? '' : 'opacity-40 pointer-events-none'}`}>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--c-text)]">File locking</p>
|
||||
<p className="text-xs text-[var(--c-muted)] mt-0.5">
|
||||
Allow contributors to lock LFS files to prevent conflicting edits on binary assets.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => toggle('lockingEnabled', !lockingEnabled)}
|
||||
disabled={update.isPending || !enabled}
|
||||
className={`relative shrink-0 inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:opacity-50 ${lockingEnabled ? 'bg-[var(--c-brand)]' : 'bg-[var(--c-border)]'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 rounded-full bg-white shadow transition-transform ${lockingEnabled ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Max file size */}
|
||||
<div className={`space-y-3 transition-opacity ${enabled ? '' : 'opacity-40 pointer-events-none'}`}>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--c-text)]">Maximum file size</p>
|
||||
<p className="text-xs text-[var(--c-muted)] mt-0.5">
|
||||
Reject LFS pushes for individual files exceeding this size. Leave blank for no limit.
|
||||
</p>
|
||||
</div>
|
||||
<form onSubmit={saveMaxSize} className="flex items-center gap-2">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={maxSizeInput}
|
||||
onChange={e => setMaxSizeInput(e.target.value)}
|
||||
disabled={!enabled}
|
||||
placeholder="Unlimited"
|
||||
className="w-36 border border-[var(--c-border)] rounded px-3 py-2 text-sm bg-[var(--c-surface)] text-[var(--c-text)] focus:outline-none focus:border-[var(--c-brand-focus)] disabled:opacity-50 pr-10"
|
||||
/>
|
||||
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-[var(--c-muted)] pointer-events-none">MB</span>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={update.isPending || !enabled}
|
||||
className="px-4 py-2 rounded bg-[var(--c-brand)] text-white text-sm font-medium hover:bg-[var(--c-brand-hover)] disabled:opacity-50"
|
||||
>
|
||||
{update.isPending ? 'Saving…' : 'Save'}
|
||||
</button>
|
||||
{saved && <span className="text-xs text-[var(--c-success)]">Saved</span>}
|
||||
</form>
|
||||
{sizeError && <p className="text-xs text-[var(--c-danger)]">{sizeError}</p>}
|
||||
</div>
|
||||
|
||||
{/* Info box */}
|
||||
<div className="rounded-lg bg-[var(--c-surface-muted)] border border-[var(--c-border)] px-4 py-3 flex gap-3">
|
||||
<svg className="shrink-0 mt-0.5 text-[var(--c-brand)]" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
||||
</svg>
|
||||
<p className="text-xs text-[var(--c-muted)] leading-relaxed">
|
||||
Git LFS must be <a href="https://git-lfs.com" target="_blank" rel="noreferrer" className="text-[var(--c-brand)] hover:underline">installed on the client</a> to push and pull tracked files.
|
||||
Add a <code className="font-mono bg-[var(--c-surface)] px-1 rounded">.gitattributes</code> file to your repository to specify which file patterns LFS should track.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Coming soon ──────────────────────────────────────────────────────────────
|
||||
|
||||
function ComingSoon({ sectionId }: { sectionId: SectionId }) {
|
||||
|
||||
Reference in New Issue
Block a user