summaryrefslogtreecommitdiff
path: root/admin-panel/src/server/services/git.service.ts
blob: 990f5df720db55d8ae4bbb76ec1febd24b203d67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { execSync } from 'child_process';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';

const REPO_BASE = process.env.GIT_REPO_PATH || '/repos';

export interface RepoInfo {
  name: string;
  path: string;
  exists: boolean;
}

export function createBareRepo(repoName: string, defaultBranch = 'main'): RepoInfo {
  const repoPath = join(REPO_BASE, repoName);

  if (existsSync(repoPath)) {
    return { name: repoName, path: repoPath, exists: true };
  }

  execSync(`git init --bare "${repoPath}"`, { stdio: 'pipe' });
  execSync(`git -C "${repoPath}" symbolic-ref HEAD refs/heads/${defaultBranch}`, {
    stdio: 'pipe',
  });

  // Set description for cgit
  const descPath = join(repoPath, 'description');
  execSync(`echo "Repository managed by admin-panel" > "${descPath}"`, { stdio: 'pipe' });

  // Enable post-update hook for git daemon
  const hookPath = join(repoPath, 'hooks', 'post-update');
  if (!existsSync(hookPath)) {
    execSync(`cp "${repoPath}/hooks/post-update.sample" "${hookPath}" 2>/dev/null || true`, {
      stdio: 'pipe',
    });
  }

  // Run git update-server-info for HTTP access
  execSync(`git -C "${repoPath}" update-server-info`, { stdio: 'pipe' });

  return { name: repoName, path: repoPath, exists: false };
}

export function setRepoDescription(repoName: string, description: string): void {
  const descPath = join(REPO_BASE, repoName, 'description');
  execSync(`echo "${description.replace(/"/g, '\\"')}" > "${descPath}"`, { stdio: 'pipe' });
}

export function repoExists(repoName: string): boolean {
  return existsSync(join(REPO_BASE, repoName));
}

export function deleteRepo(repoName: string): void {
  const repoPath = join(REPO_BASE, repoName);
  if (existsSync(repoPath)) {
    execSync(`rm -rf "${repoPath}"`, { stdio: 'pipe' });
  }
}

// --- Workspace operations for Builder agent ---

export async function cloneRepo(bareRepoPath: string, workspacePath: string): Promise<void> {
  if (existsSync(workspacePath)) {
    // Pull latest instead of re-cloning
    execSync(`git -C "${workspacePath}" fetch origin`, { stdio: 'pipe' });
    execSync(`git -C "${workspacePath}" reset --hard origin/main 2>/dev/null || true`, { stdio: 'pipe' });
    return;
  }
  mkdirSync(workspacePath, { recursive: true });
  execSync(`git clone "${bareRepoPath}" "${workspacePath}"`, { stdio: 'pipe' });

  // Configure git user for commits
  execSync(`git -C "${workspacePath}" config user.email "admin-panel@swave.lol"`, { stdio: 'pipe' });
  execSync(`git -C "${workspacePath}" config user.name "Admin Panel"`, { stdio: 'pipe' });
}

export async function createBranch(workspacePath: string, branchName: string): Promise<void> {
  // Create or switch to branch
  try {
    execSync(`git -C "${workspacePath}" checkout -b "${branchName}"`, { stdio: 'pipe' });
  } catch {
    // Branch might already exist
    execSync(`git -C "${workspacePath}" checkout "${branchName}"`, { stdio: 'pipe' });
  }
}

export async function writeFiles(
  workspacePath: string,
  files: Array<{ path: string; content: string }>
): Promise<void> {
  for (const file of files) {
    const fullPath = join(workspacePath, file.path);
    mkdirSync(dirname(fullPath), { recursive: true });
    writeFileSync(fullPath, file.content, 'utf-8');
  }
}

export async function commitAndDiff(
  workspacePath: string,
  message: string,
  branchName: string
): Promise<string> {
  execSync(`git -C "${workspacePath}" add -A`, { stdio: 'pipe' });

  // Check if there are changes to commit
  try {
    execSync(`git -C "${workspacePath}" diff --cached --quiet`, { stdio: 'pipe' });
    // No changes
    return '';
  } catch {
    // There are changes — commit them
  }

  execSync(`git -C "${workspacePath}" commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'pipe' });

  // Get diff against main
  const diff = execSync(
    `git -C "${workspacePath}" diff main..${branchName} 2>/dev/null || git -C "${workspacePath}" diff HEAD~1..HEAD`,
    { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
  );

  return diff;
}

export async function pushToGerrit(workspacePath: string): Promise<string> {
  const output = execSync(
    `git -C "${workspacePath}" push origin HEAD:refs/for/main 2>&1`,
    { encoding: 'utf-8' }
  );
  return output;
}

export function getFileList(workspacePath: string): string {
  if (!existsSync(workspacePath)) return '';
  try {
    return execSync(`find "${workspacePath}" -type f -not -path "*/.git/*" | sort`, {
      encoding: 'utf-8',
      maxBuffer: 1024 * 1024,
    });
  } catch {
    return '';
  }
}