|
|
const fs = require('fs');
|
|
|
const path = require('path');
|
|
|
const crypto = require('crypto');
|
|
|
const { execFileSync } = require('child_process');
|
|
|
const {
|
|
|
resolveCloneUrlForGit,
|
|
|
stripGitCredentials,
|
|
|
buildGitHttpAuthConfigArgs,
|
|
|
shouldApplyDefaultGitAuth,
|
|
|
} = require('./gitCloneUrl.cjs');
|
|
|
|
|
|
function gitEnv() {
|
|
|
return {
|
|
|
...process.env,
|
|
|
GIT_TERMINAL_PROMPT: '0',
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function sha16(s) {
|
|
|
return crypto.createHash('sha256').update(String(s)).digest('hex').slice(0, 16);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 浅克隆学员仓库到 .cache/student-repos/<hash>/ ,已存在则 git pull。
|
|
|
* @param {string} gitUrl 完整 https 地址(私有库可在 URL 中带 token)
|
|
|
* @param {string} rootDir 项目根
|
|
|
* @returns {string} 本地克隆目录绝对路径
|
|
|
*/
|
|
|
function ensureStudentRepo(gitUrl, rootDir) {
|
|
|
const publicUrl = String(gitUrl).trim();
|
|
|
const authCfg = buildGitHttpAuthConfigArgs(publicUrl);
|
|
|
/** 使用 extraHeader 时不要用带密码的 origin URL(避免被凭证助手改写后仍无法拉取) */
|
|
|
const originUrlForRemote = shouldApplyDefaultGitAuth(publicUrl)
|
|
|
? stripGitCredentials(publicUrl)
|
|
|
: resolveCloneUrlForGit(publicUrl);
|
|
|
const cacheRoot = path.join(rootDir, '.cache', 'student-repos');
|
|
|
fs.mkdirSync(cacheRoot, { recursive: true });
|
|
|
const dir = path.join(cacheRoot, sha16(stripGitCredentials(publicUrl)));
|
|
|
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
|
try {
|
|
|
execFileSync('git', ['-C', dir, 'remote', 'set-url', 'origin', originUrlForRemote], {
|
|
|
stdio: 'pipe',
|
|
|
env: gitEnv(),
|
|
|
});
|
|
|
execFileSync(
|
|
|
'git',
|
|
|
[...authCfg, '-C', dir, 'pull', '--ff-only'],
|
|
|
{ stdio: 'pipe', timeout: 300000, env: gitEnv() },
|
|
|
);
|
|
|
} catch {
|
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
|
execFileSync(
|
|
|
'git',
|
|
|
[...authCfg, 'clone', '--depth', '1', originUrlForRemote, dir],
|
|
|
{ stdio: 'pipe', timeout: 300000, env: gitEnv() },
|
|
|
);
|
|
|
}
|
|
|
} else {
|
|
|
execFileSync(
|
|
|
'git',
|
|
|
[...authCfg, 'clone', '--depth', '1', originUrlForRemote, dir],
|
|
|
{ stdio: 'pipe', timeout: 300000, env: gitEnv() },
|
|
|
);
|
|
|
}
|
|
|
if (!fs.existsSync(path.join(dir, '.git'))) {
|
|
|
throw new Error('git clone 后仍未发现 .git,请检查仓库地址与网络');
|
|
|
}
|
|
|
return dir;
|
|
|
}
|
|
|
|
|
|
module.exports = { ensureStudentRepo, sha16 };
|