設計目標
- 封裝
svn CLI 指令,提供 OOP 介面
- 統一錯誤處理與日誌記錄
- 支援 CI/CD 流水線自動化佈署
核心類別結構
workDir = realpath($workDir);
}
/** 統一執行入口 */
private function exec(string $cmd): array {
$full = self::BIN . ' ' . self::AUTH . ' ' . $cmd . ' 2>&1';
$out = [];
$code = 0;
exec("cd {$this->workDir} && {$full}", $out, $code);
$this->lastOutput = $out;
$this->lastCode = $code;
return [$out, $code];
}
/** 解析輸出判斷成功 */
private function assertSuccess(array $out, int $code, string $keyword): bool {
if ($code !== 0) return false;
return implode("\n", $out) !== '' && stripos(implode("\n", $out), $keyword) !== false;
}
// ---------- 公開 API ----------
public function ls(string $url): array { /* ... */ }
public function copy(string $src, string $dst, string $msg): bool { /* ... */ }
public function delete(string $url, string $msg): bool { /* ... */ }
public function move(string $src, string $dst, string $msg): bool { /* ... */ }
public function mkdir(string $url, string $msg): bool { /* ... */ }
public function diff(string $url1, string $url2): string { /* ... */ }
public function checkout(string $url, string $dest): bool { /* ... */ }
public function update(string $dest): array { /* 回傳 [rev, changedFiles] */ }
public function merge(string $target, string $source, int $revFrom, int $revTo): bool { /* ... */ }
public function commit(string $path, string $msg): bool { /* ... */ }
public function status(string $path): array { /* ... */ }
public function hasConflict(string $path): bool { /* ... */ }
public function log(string $url, int $limit = 10): array { /* ... */ }
public function info(string $url): array { /* 回傳 [rev, url, author, date] */ }
public function getHeadRevision(string $wc): int { /* ... */ }
public function getFileFirstVersion(string $url): int { /* ... */ }
public function getChangedFiles(int $revFrom, int $revTo): array { /* ... */ }
public function getChangedInfo(int $revFrom, int $revTo): string { /* ... */ }
public function getFileContent(string $url, int $rev): string { /* ... */ }
}
關鍵方法實現片段
更新並取得版本號
public function update(string $dest): array {
[$out, $code] = $this->exec("up {$dest}");
if ($code !== 0) throw new RuntimeException("SVN up failed: " . implode("\n", $out));
$rev = 0;
foreach ($out as $line) {
if (preg_match('/Updated to revision (\d+)/', $line, $m)) {
$rev = (int)$m[1];
break;
}
}
return [$rev, $out];
}
檢測衝突
public function hasConflict(string $path): bool {
[$out] = $this->exec("st {$path}");
foreach ($out as $line) {
if (str_starts_with(trim($line), 'C') || str_starts_with(trim($line), '!')) {
return true; // Conflict 或 Missing
}
}
return false;
}
取得檔案指定版本內容
public function getFileContent(string $url, int $rev): string {
[$out, $code] = $this->exec("cat -r {$rev} {$url}");
if ($code !== 0) throw new RuntimeException("SVN cat failed");
return implode("\n", $out);
}
實戰應用場景
- 自動化發布:Jenkins/GitLab CI 階段
svn copy trunk tags/1.2.3 -m "Release 1.2.3"
- 程式碼審查輔助:MR 建立時自動
svn diff 產出變更報表
- 熱修復回滾:
svn merge -r HEAD:12345 . 快速還原特定版本
最佳實踐與坑點
- 權限隔離:專用 SVN 帳號(robot),僅授予必要路徑讀寫權限。
- 非互動模式:
--non-interactive --no-auth-cache 避免提示阻塞 CI。
- 工作目錄乾淨:操作前先
svn status 確認無本地修改,否則 update/merge 易產生衝突。
- 編碼一致:確保伺服器、資料庫、PHP 檔案皆為 UTF-8,避免中文檔名亂碼。
- 超時控制:大型倉庫
checkout/update 可能超過 PHP max_execution_time,建議 CLI 模式執行並設 set_time_limit(0)。
擴展方向
- 引入
symfony/process 替代 exec(),獲得即時輸出流、信號處理、跨平台相容。
- 實作
SvnRepository 介面,支援 svn://、https://、file:// 多協議。
- 整合
Webhook 觸發自動同步至鏡像倉庫。