main • app/controllers/PullController.php
<?php
class PullController
{
private array $cfg;
public function __construct(array $cfg){ $this->cfg=$cfg; }
public function list(int $repoId): void {
$db = db();
$uid = auth_user_id();
if (!repo_can_read($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$repo = repo_get($db, $repoId);
$st = $db->prepare("SELECT pr.*, u.username as author_name
FROM pull_requests pr
JOIN users u ON u.id = pr.author_id
WHERE pr.repo_id=?
ORDER BY pr.id DESC");
$st->execute([$repoId]);
$prs = $st->fetchAll();
$canWrite = repo_can_write($db, $repoId, $uid);
$title = $repo['owner_username'] . "/" . $repo['name'] . " / Pull Requests";
require __DIR__ . '/../views/pulls/list.php';
}
public function showCreate(int $repoId): void {
$db = db();
$uid = require_auth();
if (!repo_can_write($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$repo = repo_get($db, $repoId);
$st = $db->prepare("SELECT name FROM branches WHERE repo_id=? ORDER BY name ASC");
$st->execute([$repoId]);
$branches = $st->fetchAll();
$title = $repo['owner_username'] . "/" . $repo['name'] . " / New PR";
require __DIR__ . '/../views/pulls/create.php';
}
public function create(int $repoId): void {
$db = db();
$uid = require_auth();
csrf_verify();
if (!repo_can_write($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$repo = repo_get($db, $repoId);
$title = trim($_POST['title'] ?? '');
$body = trim($_POST['body'] ?? '');
$source = safe_branch($_POST['source_branch'] ?? '');
$target = safe_branch($_POST['target_branch'] ?? '');
if ($title === '' || strlen($title) > 255) {
flash_set('err','Title required (max 255).');
redirect("/r/$repoId/pulls/new");
}
if ($source === $target) {
flash_set('err','Source and target branches must be different.');
redirect("/r/$repoId/pulls/new");
}
$st = $db->prepare("SELECT COALESCE(MAX(number),0)+1 as nextn FROM pull_requests WHERE repo_id=?");
$st->execute([$repoId]);
$next = (int)($st->fetch()['nextn'] ?? 1);
$st = $db->prepare("INSERT INTO pull_requests
(repo_id, number, title, body, state, source_branch, target_branch, author_id, created_at, updated_at)
VALUES (?,?,?,?, 'open', ?, ?, ?, ?, ?)");
$st->execute([$repoId, $next, $title, $body, $source, $target, $uid, now(), now()]);
flash_set('ok', "PR created (#$next).");
redirect("/r/$repoId/pulls/$next");
}
public function show(int $repoId, int $number): void {
$db = db();
$uid = auth_user_id();
if (!repo_can_read($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$repo = repo_get($db, $repoId);
$st = $db->prepare("SELECT pr.*, u.username as author_name
FROM pull_requests pr
JOIN users u ON u.id = pr.author_id
WHERE pr.repo_id=? AND pr.number=? LIMIT 1");
$st->execute([$repoId, $number]);
$pr = $st->fetch();
if (!$pr) { http_response_code(404); exit("PR not found"); }
$blobs = new BlobStore($this->cfg['storage_base']);
$engine = new RepoEngine($blobs);
$diffEngine = new DiffEngine($blobs);
$sourceHead = $engine->getBranchHead($db, $repoId, $pr['source_branch']);
$targetHead = $engine->getBranchHead($db, $repoId, $pr['target_branch']);
$snapA = $targetHead ? $engine->getSnapshot($db, (int)$targetHead) : [];
$snapB = $sourceHead ? $engine->getSnapshot($db, (int)$sourceHead) : [];
$changes = $diffEngine->diffSnapshots($snapA, $snapB);
$canWrite = repo_can_write($db, $repoId, $uid);
$title = $repo['owner_username'] . "/" . $repo['name'] . " / PR #" . $number;
require __DIR__ . '/../views/pulls/show.php';
}
public function merge(int $repoId, int $number): void {
$db = db();
$uid = require_auth();
csrf_verify();
if (!repo_can_write($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$st = $db->prepare("SELECT * FROM pull_requests WHERE repo_id=? AND number=? LIMIT 1");
$st->execute([$repoId, $number]);
$pr = $st->fetch();
if (!$pr) { http_response_code(404); exit("PR not found"); }
if ($pr['state'] !== 'open') { flash_set('err','PR is not open.'); redirect("/r/$repoId/pulls/$number"); }
$blobs = new BlobStore($this->cfg['storage_base']);
$engine = new RepoEngine($blobs);
$sourceHead = $engine->getBranchHead($db, $repoId, $pr['source_branch']);
if (!$sourceHead) { flash_set('err','Source branch has no commits.'); redirect("/r/$repoId/pulls/$number"); }
$st = $db->prepare("UPDATE branches SET head_commit_id=? WHERE repo_id=? AND name=?");
$st->execute([(int)$sourceHead, $repoId, $pr['target_branch']]);
$st = $db->prepare("UPDATE pull_requests SET state='merged', updated_at=? WHERE id=?");
$st->execute([now(), (int)$pr['id']]);
flash_set('ok', "PR merged (fast-forward).");
redirect("/r/$repoId/pulls/$number");
}
}