Build and Deploy Verso / deploy (push) Successful in 12m35s
Branch: - New "Branch" text field (default: main) stored in gitSyncBranch on the project. Used for git fetch, symbolic-ref, and push target. Validated server-side: alphanumeric + / _ . - only, no .. First-push confirmation: - When pushFiles is enabled and the user hasn't confirmed for the current subPath, clicking "Push now" shows an inline warning: "The <subPath> directory will be completely replaced …" with "Yes, push and replace" / "Cancel" buttons. - Confirmation is stored in localStorage keyed by projectId+subPath, so it's shown again if the subPath is changed. - Auto-push bypasses the dialog (user already opted in explicitly). PDF-only replace: - Already correct with the fetch-first approach: only the specific file at pdfPath is written; other files in the same directory (e.g. output/old.pdf) are preserved from the fetched remote state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
3.7 KiB
JavaScript
116 lines
3.7 KiB
JavaScript
import logger from '@overleaf/logger'
|
|
import GitSyncHandler from './GitSyncHandler.mjs'
|
|
import SessionManager from '../Authentication/SessionManager.mjs'
|
|
import { expressify } from '@overleaf/promise-utils'
|
|
|
|
async function configureGitSync(req, res) {
|
|
const projectId = req.params.project_id
|
|
const {
|
|
remoteUrl,
|
|
subPath = '',
|
|
pdfPath = '',
|
|
pushFiles = true,
|
|
pushPdf = true,
|
|
branch = 'main',
|
|
} = req.body
|
|
|
|
if (typeof remoteUrl !== 'string' || remoteUrl.trim() === '') {
|
|
return res.status(400).json({ error: 'remoteUrl is required' })
|
|
}
|
|
|
|
const trimmedUrl = remoteUrl.trim()
|
|
if (!trimmedUrl.startsWith('https://') && !trimmedUrl.startsWith('git@')) {
|
|
return res.status(400).json({ error: 'remoteUrl must start with https:// or git@' })
|
|
}
|
|
|
|
const trimmedSubPath = String(subPath).trim().replace(/^\/+|\/+$/g, '')
|
|
if (trimmedSubPath.includes('..')) {
|
|
return res.status(400).json({ error: 'subPath must not contain ..' })
|
|
}
|
|
|
|
const trimmedPdfPath = String(pdfPath).trim().replace(/^\/+/, '')
|
|
if (trimmedPdfPath.includes('..')) {
|
|
return res.status(400).json({ error: 'pdfPath must not contain ..' })
|
|
}
|
|
|
|
const trimmedBranch = String(branch).trim()
|
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(trimmedBranch) || trimmedBranch.includes('..')) {
|
|
return res.status(400).json({ error: 'Invalid branch name' })
|
|
}
|
|
|
|
await GitSyncHandler.setConfig(projectId, {
|
|
remoteUrl: trimmedUrl,
|
|
subPath: trimmedSubPath,
|
|
pdfPath: trimmedPdfPath,
|
|
pushFiles: Boolean(pushFiles),
|
|
pushPdf: Boolean(pushPdf),
|
|
branch: trimmedBranch,
|
|
})
|
|
logger.debug({ projectId }, 'git sync: config saved')
|
|
res.sendStatus(204)
|
|
}
|
|
|
|
async function pushToGit(req, res) {
|
|
const projectId = req.params.project_id
|
|
try {
|
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
|
const { buildId, clsiServerId } = req.body ?? {}
|
|
const { remoteUrl, subPath, pdfPath, pushFiles, pushPdf, branch } =
|
|
await GitSyncHandler.getConfig(projectId)
|
|
if (!remoteUrl) {
|
|
return res.status(400).json({ error: 'No git remote configured for this project' })
|
|
}
|
|
logger.debug({ projectId }, 'git sync: starting push')
|
|
await GitSyncHandler.pushToRemote(projectId, remoteUrl, subPath, {
|
|
pdfPath,
|
|
pdfBuildId: buildId,
|
|
pdfClsiServerId: clsiServerId,
|
|
userId,
|
|
pushFiles,
|
|
pushPdf,
|
|
branch,
|
|
})
|
|
res.sendStatus(204)
|
|
} catch (err) {
|
|
logger.warn({ err, projectId }, 'git sync: push failed')
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
}
|
|
|
|
async function pullFromGit(req, res) {
|
|
const projectId = req.params.project_id
|
|
try {
|
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
|
const { remoteUrl, subPath } = await GitSyncHandler.getConfig(projectId)
|
|
if (!remoteUrl) {
|
|
return res.status(400).json({ error: 'No git remote configured for this project' })
|
|
}
|
|
logger.debug({ projectId }, 'git sync: starting pull')
|
|
await GitSyncHandler.pullFromRemote(projectId, remoteUrl, subPath, userId)
|
|
res.sendStatus(204)
|
|
} catch (err) {
|
|
logger.warn({ err, projectId }, 'git sync: pull failed')
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
}
|
|
|
|
async function getGitSyncConfig(req, res) {
|
|
const projectId = req.params.project_id
|
|
const config = await GitSyncHandler.getConfig(projectId)
|
|
res.json({
|
|
remoteUrl: config.remoteUrl ?? '',
|
|
subPath: config.subPath,
|
|
pdfPath: config.pdfPath,
|
|
pushFiles: config.pushFiles,
|
|
pushPdf: config.pushPdf,
|
|
branch: config.branch,
|
|
})
|
|
}
|
|
|
|
export default {
|
|
configureGitSync: expressify(configureGitSync),
|
|
pushToGit: expressify(pushToGit),
|
|
pullFromGit: expressify(pullFromGit),
|
|
getGitSyncConfig: expressify(getGitSyncConfig),
|
|
}
|