系统功能说明
这个PHP版本控制系统实现了以下核心功能:
1. 用户认证与权限管理
- 用户注册、登录和登出
- 三级权限系统:管理员(admin)、编辑者(editor)和查看者(viewer)
- 基于角色的访问控制(RBAC)
2. 仓库管理
- 创建新仓库
- 查看仓库列表和详情
- 仓库统计信息(提交数、分支数、贡献者)
3. 文档版本控制
- 上传文档到仓库
- 查看文档历史版本
- 检索特定版本的文档内容
- 类似Git的提交机制
4. 分支管理
- 创建新分支
- 查看分支列表
- 合并分支到主分支
5. 权限控制
- 仓库级别的权限控制
- 基于用户角色的操作限制
安装与使用
- 创建数据库并运行
install.php
初始化数据库结构 - 配置
config.php
中的数据库连接信息 - 将所有文件上传到Web服务器
- 访问系统首页进行注册和登录
- 管理员可以创建仓库,用户可以上传文档并管理版本
这个系统提供了完整的文档版本控制功能,适合团队协作管理文档,支持权限分级和版本历史追踪。
以下是一些视图文件的示例,用于展示用户界面:
/* css/style.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
header {
background-color: #2c3e50;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
header h1 {
display: inline-block;
margin-right: 2rem;
}
header nav {
display: inline-block;
}
header nav a {
color: white;
text-decoration: none;
margin-right: 1rem;
padding: 0.5rem;
border-radius: 4px;
transition: background-color 0.3s;
}
header nav a:hover {
background-color: #34495e;
}
main {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 2rem;
margin-bottom: 2rem;
}
footer {
text-align: center;
padding: 1rem 0;
color: #7f8c8d;
font-size: 0.9rem;
}
/* Home Page */
.hero {
text-align: center;
padding: 3rem 0;
background-color: #3498db;
color: white;
border-radius: 8px;
margin-bottom: 2rem;
}
.hero h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.2rem;
margin-bottom: 2rem;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.cta a {
display: inline-block;
margin: 0 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 4px;
text-decoration: none;
font-weight: bold;
}
.btn {
background-color: #2ecc71;
color: white;
}
.btn-secondary {
background-color: #95a5a6;
color: white;
}
.features {
display: flex;
justify-content: space-between;
margin-top: 2rem;
}
.feature {
flex: 1;
padding: 1.5rem;
margin: 0 0.5rem;
background-color: #f8f9fa;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.feature h3 {
margin-bottom: 1rem;
color: #2c3e50;
}
/* Dashboard */
.dashboard-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #ecf0f1;
}
.dashboard-header h2 {
margin-bottom: 0.5rem;
}
.repositories h3 {
margin-bottom: 1rem;
}
.repo-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.repo-card {
background-color: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
transition: transform 0.3s, box-shadow 0.3s;
}
.repo-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.repo-card h4 {
margin-bottom: 0.5rem;
}
.repo-card h4 a {
color: #3498db;
text-decoration: none;
}
.repo-card h4 a:hover {
text-decoration: underline;
}
.repo-meta {
margin-top: 1rem;
font-size: 0.9rem;
color: #7f8c8d;
}
.repo-meta span {
margin-right: 1rem;
}
/* Repository Page */
.repo-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #ecf0f1;
}
.repo-header h2 {
margin-bottom: 0.5rem;
}
.repo-actions {
margin-top: 1rem;
}
.repo-actions a {
margin-right: 0.5rem;
}
.repo-stats {
display: flex;
margin-bottom: 2rem;
}
.stat {
flex: 1;
padding: 1rem;
background-color: #f8f9fa;
border-radius: 8px;
text-align: center;
margin: 0 0.5rem;
}
.stat h4 {
margin-bottom: 0.5rem;
color: #7f8c8d;
}
.stat p {
font-size: 1.5rem;
font-weight: bold;
color: #2c3e50;
}
.repo-latest-commit, .repo-branches, .repo-documents {
margin-bottom: 2rem;
}
.repo-latest-commit h3, .repo-branches h3, .repo-documents h3 {
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #ecf0f1;
}
.commit {
padding: 1rem;
background-color: #f8f9fa;
border-radius: 8px;
}
.commit-message {
margin-bottom: 0.5rem;
font-weight: bold;
}
.commit-meta {
font-size: 0.9rem;
color: #7f8c8d;
}
.commit-meta span {
margin-right: 1rem;
}
.repo-branches ul {
list-style-type: none;
}
.repo-branches li {
padding: 0.5rem 0;
border-bottom: 1px solid #ecf0f1;
}
.document-list {
width: 100%;
border-collapse: collapse;
}
.document-list th, .document-list td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #ecf0f1;
}
.document-list th {
background-color: #f8f9fa;
font-weight: bold;
}
.document-list tr:hover {
background-color: #f8f9fa;
}
/* Forms */
.form-container {
max-width: 600px;
margin: 0 auto;
}
.form-container h2 {
margin-bottom: 1.5rem;
text-align: center;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"],
.form-group input[type="file"],
.form-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group textarea {
resize: vertical;
}
.form-group small {
display: block;
margin-top: 0.25rem;
color: #7f8c8d;
font-size: 0.9rem;
}
.form-actions {
text-align: center;
margin-top: 2rem;
}
.form-actions button {
margin: 0 0.5rem;
}
.error {
padding: 1rem;
margin-bottom: 1rem;
background-color: #f8d7da;
color: #721c24;
border-radius: 4px;
border: 1px solid #f5c6cb;
}
/* Responsive Design */
@media (max-width: 768px) {
header {
text-align: center;
}
header h1 {
display: block;
margin-bottom: 1rem;
}
.features {
flex-direction: column;
}
.feature {
margin: 0.5rem 0;
}
.repo-stats {
flex-direction: column;
}
.stat {
margin: 0.5rem 0;
}
.repo-list {
grid-template-columns: 1fr;
}
}
view
<!-- views/upload.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Document - <?php echo htmlspecialchars($repository['name']); ?></title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Document Version Control System</h1>
<nav>
<a href="index.php">Home</a>
<a href="index.php?action=home">Dashboard</a>
<a href="index.php?action=repo&id=<?php echo $repository['id']; ?>">Repository</a>
<a href="index.php?action=logout">Logout</a>
</nav>
</header>
<main>
<div class="form-container">
<h2>Upload Document</h2>
<?php if (isset($error)): ?>
<div class="error"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form action="index.php?action=upload&id=<?php echo $repository['id']; ?>" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Select Document</label>
<input type="file" id="file" name="file" required>
<small>Allowed file types: <?php echo implode(', ', Config::ALLOWED_EXTENSIONS); ?></small>
</div>
<div class="form-group">
<label for="message">Commit Message</label>
<textarea id="message" name="message" rows="3"></textarea>
</div>
<div class="form-actions">
<button type="submit" class="btn">Upload Document</button>
<a href="index.php?action=repo&id=<?php echo $repository['id']; ?>" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</main>
<footer>
<p>© <?php echo date('Y'); ?> Document Version Control System</p>
</footer>
</div>
</body>
</html>
<!-- views/repo.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($repository['name']); ?> - Document VCS</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Document Version Control System</h1>
<nav>
<a href="index.php">Home</a>
<a href="index.php?action=home">Dashboard</a>
<a href="index.php?action=logout">Logout</a>
</nav>
</header>
<main>
<div class="repo-header">
<h2><?php echo htmlspecialchars($repository['name']); ?></h2>
<p><?php echo htmlspecialchars($repository['description']); ?></p>
<div class="repo-actions">
<?php if ($auth->hasPermission('write', $repository['id'])): ?>
<a href="index.php?action=upload&id=<?php echo $repository['id']; ?>" class="btn">Upload Document</a>
<a href="index.php?action=create_branch&repo_id=<?php echo $repository['id']; ?>" class="btn">Create Branch</a>
<a href="index.php?action=merge_branch&repo_id=<?php echo $repository['id']; ?>" class="btn">Merge Branch</a>
<?php endif; ?>
</div>
</div>
<div class="repo-stats">
<div class="stat">
<h4>Commits</h4>
<p><?php echo $stats['commit_count']; ?></p>
</div>
<div class="stat">
<h4>Branches</h4>
<p><?php echo $stats['branch_count']; ?></p>
</div>
<div class="stat">
<h4>Contributors</h4>
<p><?php echo count($stats['contributors']); ?></p>
</div>
</div>
<div class="repo-latest-commit">
<h3>Latest Commit</h3>
<?php if (isset($repository['latest_commit'])): ?>
<div class="commit">
<p class="commit-message"><?php echo htmlspecialchars($repository['latest_commit']['message']); ?></p>
<div class="commit-meta">
<span>Hash: <?php echo substr($repository['latest_commit']['hash'], 0, 7); ?></span>
<span>Author: <?php echo htmlspecialchars($repository['latest_commit']['author']); ?></span>
<span>Date: <?php echo $repository['latest_commit']['date']; ?></span>
</div>
</div>
<?php else: ?>
<p>No commits yet</p>
<?php endif; ?>
</div>
<div class="repo-branches">
<h3>Branches</h3>
<ul>
<?php foreach ($repository['branches'] as $branch): ?>
<li><?php echo htmlspecialchars($branch); ?></li>
<?php endforeach; ?>
</ul>
</div>
<div class="repo-documents">
<h3>Documents</h3>
<?php if (empty($documents)): ?>
<p>No documents found.</p>
<?php else: ?>
<table class="document-list">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($documents as $doc): ?>
<tr>
<td><?php echo htmlspecialchars($doc['name']); ?></td>
<td>
<a href="index.php?action=history&repo_id=<?php echo $repository['id']; ?>&file=<?php echo urlencode($doc['name']); ?>">View History</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</main>
<footer>
<p>© <?php echo date('Y'); ?> Document Version Control System</p>
</footer>
</div>
</body>
</html>
<!-- views/dashboard.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Document VCS</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Document Version Control System</h1>
<nav>
<a href="index.php">Home</a>
<a href="index.php?action=home">Dashboard</a>
<?php if ($auth->getUser()['role'] === Config::ROLE_ADMIN): ?>
<a href="index.php?action=create_repo">Create Repository</a>
<?php endif; ?>
<a href="index.php?action=logout">Logout</a>
</nav>
</header>
<main>
<div class="dashboard-header">
<h2>Welcome, <?php echo htmlspecialchars($auth->getUser()['username']); ?></h2>
<p>Role: <?php echo ucfirst($auth->getUser()['role']); ?></p>
</div>
<div class="repositories">
<h3>Your Repositories</h3>
<?php if (empty($repositories)): ?>
<p>No repositories found. <?php if ($auth->getUser()['role'] === Config::ROLE_ADMIN): ?>
<a href="index.php?action=create_repo">Create one</a>
<?php endif; ?></p>
<?php else: ?>
<div class="repo-list">
<?php foreach ($repositories as $repo): ?>
<div class="repo-card">
<h4><a href="index.php?action=repo&id=<?php echo $repo['id']; ?>"><?php echo htmlspecialchars($repo['name']); ?></a></h4>
<p><?php echo htmlspecialchars($repo['description']); ?></p>
<div class="repo-meta">
<span>Created: <?php echo date('M j, Y', strtotime($repo['created_at'])); ?></span>
<span>Owner: <?php echo htmlspecialchars($repo['owner_id']); ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</main>
<footer>
<p>© <?php echo date('Y'); ?> Document Version Control System</p>
</footer>
</div>
</body>
</html>
<!-- views/home.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document Version Control System</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Document Version Control System</h1>
<nav>
<a href="index.php">Home</a>
<?php if ($auth->isLoggedIn()): ?>
<a href="index.php?action=logout">Logout</a>
<?php else: ?>
<a href="index.php?action=login">Login</a>
<a href="index.php?action=register">Register</a>
<?php endif; ?>
</nav>
</header>
<main>
<div class="hero">
<h2>Welcome to Document VCS</h2>
<p>A powerful version control system for managing your documents with Git-like features.</p>
<?php if (!$auth->isLoggedIn()): ?>
<div class="cta">
<a href="index.php?action=login" class="btn">Login</a>
<a href="index.php?action=register" class="btn btn-secondary">Register</a>
</div>
<?php else: ?>
<div class="cta">
<a href="index.php?action=home" class="btn">Go to Dashboard</a>
</div>
<?php endif; ?>
</div>
<div class="features">
<div class="feature">
<h3>Version Control</h3>
<p>Track changes, view history, and revert to previous versions of your documents.</p>
</div>
<div class="feature">
<h3>Branching & Merging</h3>
<p>Create branches for experimental changes and merge them back when ready.</p>
</div>
<div class="feature">
<h3>Permission Management</h3>
<p>Control who can view, edit, or administer your repositories with fine-grained permissions.</p>
</div>
</div>
</main>
<footer>
<p>© <?php echo date('Y'); ?> Document Version Control System</p>
</footer>
</div>
</body>
</html>
<?php
// config.php - 配置文件
class Config {
// 数据库配置
const DB_HOST = 'localhost';
const DB_NAME = 'document_vcs';
const DB_USER = 'root';
const DB_PASS = '';
// 文件存储路径
const REPO_PATH = __DIR__ . '/repositories/';
const UPLOAD_PATH = __DIR__ . '/uploads/';
// 权限级别
const ROLE_ADMIN = 'admin'; // 管理员:完全控制
const ROLE_EDITOR = 'editor'; // 编辑者:可编辑、提交
const ROLE_VIEWER = 'viewer'; // 查看者:只读权限
// 系统设置
const MAX_FILE_SIZE = 10485760; // 10MB
const ALLOWED_EXTENSIONS = ['doc', 'docx', 'txt', 'pdf', 'md'];
}
// db.php - 数据库连接与操作
class Database {
private static $instance = null;
private $connection;
private function __construct() {
try {
$this->connection = new PDO(
"mysql:host=" . Config::DB_HOST . ";dbname=" . Config::DB_NAME,
Config::DB_USER,
Config::DB_PASS
);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
public function query($sql, $params = []) {
try {
$stmt = $this->connection->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (PDOException $e) {
die("Query failed: " . $e->getMessage());
}
}
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO $table ($columns) VALUES ($placeholders)";
$this->query($sql, array_values($data));
return $this->connection->lastInsertId();
}
public function update($table, $data, $where, $whereParams = []) {
$setParts = [];
foreach ($data as $key => $value) {
$setParts[] = "$key = ?";
}
$setClause = implode(', ', $setParts);
$sql = "UPDATE $table SET $setClause WHERE $where";
$params = array_merge(array_values($data), $whereParams);
$this->query($sql, $params);
return $this->connection->rowCount();
}
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM $table WHERE $where";
$this->query($sql, $params);
return $this->connection->rowCount();
}
public function select($table, $where = '', $params = [], $columns = '*') {
$sql = "SELECT $columns FROM $table";
if ($where) {
$sql .= " WHERE $where";
}
return $this->query($sql, $params)->fetchAll(PDO::FETCH_ASSOC);
}
public function selectOne($table, $where = '', $params = [], $columns = '*') {
$sql = "SELECT $columns FROM $table";
if ($where) {
$sql .= " WHERE $where";
}
$result = $this->query($sql, $params)->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}
}
// auth.php - 用户认证与权限管理
class Auth {
private $db;
private $user = null;
public function __construct() {
$this->db = Database::getInstance();
session_start();
if (isset($_SESSION['user_id'])) {
$this->user = $this->db->selectOne(
'users',
'id = ?',
[$_SESSION['user_id']]
);
}
}
public function login($username, $password) {
$user = $this->db->selectOne(
'users',
'username = ?',
[$username]
);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$this->user = $user;
return true;
}
return false;
}
public function logout() {
session_destroy();
$this->user = null;
}
public function register($username, $email, $password, $role = Config::ROLE_VIEWER) {
// 检查用户名是否已存在
if ($this->db->selectOne('users', 'username = ?', [$username])) {
throw new Exception("Username already exists");
}
// 检查邮箱是否已存在
if ($this->db->selectOne('users', 'email = ?', [$email])) {
throw new Exception("Email already exists");
}
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
return $this->db->insert('users', [
'username' => $username,
'email' => $email,
'password' => $hashedPassword,
'role' => $role,
'created_at' => date('Y-m-d H:i:s')
]);
}
public function isLoggedIn() {
return $this->user !== null;
}
public function getUser() {
return $this->user;
}
public function hasPermission($permission, $documentId = null) {
if (!$this->isLoggedIn()) {
return false;
}
// 管理员拥有所有权限
if ($this->user['role'] === Config::ROLE_ADMIN) {
return true;
}
// 检查文档特定权限
if ($documentId) {
$docPermission = $this->db->selectOne(
'document_permissions',
'document_id = ? AND user_id = ?',
[$documentId, $this->user['id']]
);
if ($docPermission) {
return $docPermission['permission'] === $permission;
}
}
// 默认权限检查
switch ($permission) {
case 'read':
return in_array($this->user['role'], [Config::ROLE_EDITOR, Config::ROLE_VIEWER]);
case 'write':
return $this->user['role'] === Config::ROLE_EDITOR;
case 'admin':
return false;
default:
return false;
}
}
public function requireLogin() {
if (!$this->isLoggedIn()) {
header('Location: login.php');
exit;
}
}
public function requirePermission($permission, $documentId = null) {
$this->requireLogin();
if (!$this->hasPermission($permission, $documentId)) {
http_response_code(403);
die("You don't have permission to perform this action");
}
}
}
// repository.php - 仓库管理与版本控制
class Repository {
private $db;
private $auth;
private $repoPath;
public function __construct($auth) {
$this->db = Database::getInstance();
$this->auth = $auth;
$this->repoPath = Config::REPO_PATH;
// 确保仓库目录存在
if (!file_exists($this->repoPath)) {
mkdir($this->repoPath, 0755, true);
}
}
public function createRepository($name, $description = '') {
$this->auth->requirePermission('admin');
// 检查仓库名称是否已存在
if ($this->db->selectOne('repositories', 'name = ?', [$name])) {
throw new Exception("Repository already exists");
}
// 创建数据库记录
$repoId = $this->db->insert('repositories', [
'name' => $name,
'description' => $description,
'owner_id' => $this->auth->getUser()['id'],
'created_at' => date('Y-m-d H:i:s')
]);
// 创建仓库目录
$repoDir = $this->repoPath . $repoId;
if (!mkdir($repoDir, 0755, true)) {
throw new Exception("Failed to create repository directory");
}
// 初始化Git仓库
$this->executeGitCommand($repoDir, 'init');
$this->executeGitCommand($repoDir, 'config user.name "' . $this->auth->getUser()['username'] . '"');
$this->executeGitCommand($repoDir, 'config user.email "' . $this->auth->getUser()['email'] . '"');
// 创建初始提交
file_put_contents($repoDir . '/README.md', "# $name\n\n$description");
$this->executeGitCommand($repoDir, 'add README.md');
$this->executeGitCommand($repoDir, 'commit -m "Initial commit"');
return $repoId;
}
public function getRepositories() {
$this->auth->requireLogin();
$userId = $this->auth->getUser()['id'];
// 管理员可以查看所有仓库
if ($this->auth->getUser()['role'] === Config::ROLE_ADMIN) {
return $this->db->select('repositories');
}
// 其他用户只能查看有权限的仓库
return $this->db->select(
'repositories r',
'r.owner_id = ? OR EXISTS (
SELECT 1 FROM repository_permissions rp
WHERE rp.repository_id = r.id AND rp.user_id = ?
)',
[$userId, $userId]
);
}
public function getRepository($id) {
$this->auth->requirePermission('read', $id);
$repo = $this->db->selectOne('repositories', 'id = ?', [$id]);
if (!$repo) {
throw new Exception("Repository not found");
}
$repoDir = $this->repoPath . $id;
// 获取最新提交信息
$latestCommit = $this->executeGitCommand($repoDir, 'log -1 --pretty=format:"%H|%s|%an|%ad" --date=short');
if ($latestCommit) {
list($hash, $message, $author, $date) = explode('|', $latestCommit);
$repo['latest_commit'] = [
'hash' => $hash,
'message' => $message,
'author' => $author,
'date' => $date
];
}
// 获取分支列表
$branchesOutput = $this->executeGitCommand($repoDir, 'branch -l');
$branches = [];
if ($branchesOutput) {
$lines = explode("\n", trim($branchesOutput));
foreach ($lines as $line) {
$branch = trim(str_replace('*', '', $line));
$branches[] = $branch;
}
}
$repo['branches'] = $branches;
return $repo;
}
public function addDocument($repoId, $file, $message = '') {
$this->auth->requirePermission('write', $repoId);
// 验证文件
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception("File upload error");
}
if ($file['size'] > Config::MAX_FILE_SIZE) {
throw new Exception("File too large");
}
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, Config::ALLOWED_EXTENSIONS)) {
throw new Exception("File type not allowed");
}
$repoDir = $this->repoPath . $repoId;
$fileName = $file['name'];
$filePath = $repoDir . '/' . $fileName;
// 移动文件到仓库目录
if (!move_uploaded_file($file['tmp_name'], $filePath)) {
throw new Exception("Failed to save file");
}
// 添加到Git并提交
$this->executeGitCommand($repoDir, 'add "' . $fileName . '"');
$commitMessage = $message ?: "Add document: $fileName";
$this->executeGitCommand($repoDir, 'commit -m "' . $commitMessage . '"');
// 记录到数据库
$commitHash = trim($this->executeGitCommand($repoDir, 'rev-parse HEAD'));
$this->db->insert('documents', [
'repository_id' => $repoId,
'name' => $fileName,
'file_path' => $filePath,
'commit_hash' => $commitHash,
'uploaded_by' => $this->auth->getUser()['id'],
'uploaded_at' => date('Y-m-d H:i:s')
]);
return $commitHash;
}
public function getDocuments($repoId) {
$this->auth->requirePermission('read', $repoId);
$repoDir = $this->repoPath . $repoId;
// 获取Git中的文件列表
$filesOutput = $this->executeGitCommand($repoDir, 'ls-files');
$files = [];
if ($filesOutput) {
$lines = explode("\n", trim($filesOutput));
foreach ($lines as $line) {
$files[] = [
'name' => $line,
'path' => $repoDir . '/' . $line
];
}
}
return $files;
}
public function getDocumentHistory($repoId, $fileName) {
$this->auth->requirePermission('read', $repoId);
$repoDir = $this->repoPath . $repoId;
// 获取文件历史记录
$historyOutput = $this->executeGitCommand(
$repoDir,
'log --pretty=format:"%H|%s|%an|%ad" --date=short -- "' . $fileName . '"'
);
$history = [];
if ($historyOutput) {
$lines = explode("\n", trim($historyOutput));
foreach ($lines as $line) {
list($hash, $message, $author, $date) = explode('|', $line);
$history[] = [
'hash' => $hash,
'message' => $message,
'author' => $author,
'date' => $date
];
}
}
return $history;
}
public function getDocumentVersion($repoId, $fileName, $commitHash) {
$this->auth->requirePermission('read', $repoId);
$repoDir = $this->repoPath . $repoId;
// 获取特定版本的文件内容
$content = $this->executeGitCommand($repoDir, 'show ' . $commitHash . ':"' . $fileName . '"');
return $content;
}
public function createBranch($repoId, $branchName, $sourceBranch = 'main') {
$this->auth->requirePermission('write', $repoId);
$repoDir = $this->repoPath . $repoId;
// 检查分支是否已存在
$branchesOutput = $this->executeGitCommand($repoDir, 'branch -l');
if ($branchesOutput && strpos($branchesOutput, $branchName) !== false) {
throw new Exception("Branch already exists");
}
// 创建新分支
$this->executeGitCommand($repoDir, 'branch ' . $branchName . ' ' . $sourceBranch);
// 记录到数据库
$this->db->insert('branches', [
'repository_id' => $repoId,
'name' => $branchName,
'source_branch' => $sourceBranch,
'created_by' => $this->auth->getUser()['id'],
'created_at' => date('Y-m-d H:i:s')
]);
return true;
}
public function mergeBranch($repoId, $sourceBranch, $targetBranch = 'main') {
$this->auth->requirePermission('write', $repoId);
$repoDir = $this->repoPath . $repoId;
// 切换到目标分支
$this->executeGitCommand($repoDir, 'checkout ' . $targetBranch);
// 合并源分支
$mergeOutput = $this->executeGitCommand($repoDir, 'merge ' . $sourceBranch . ' --no-edit');
// 记录合并操作
$this->db->insert('merges', [
'repository_id' => $repoId,
'source_branch' => $sourceBranch,
'target_branch' => $targetBranch,
'merged_by' => $this->auth->getUser()['id'],
'merged_at' => date('Y-m-d H:i:s'),
'status' => 'success'
]);
return $mergeOutput;
}
public function getRepositoryStats($repoId) {
$this->auth->requirePermission('read', $repoId);
$repoDir = $this->repoPath . $repoId;
// 获取提交总数
$commitCount = trim($this->executeGitCommand($repoDir, 'rev-list --count HEAD'));
// 获取分支数量
$branchesOutput = $this->executeGitCommand($repoDir, 'branch -l');
$branchCount = $branchesOutput ? count(explode("\n", trim($branchesOutput))) : 0;
// 获取贡献者数量
$contributorsOutput = $this->executeGitCommand($repoDir, 'shortlog -s | cut -c8-');
$contributors = [];
if ($contributorsOutput) {
$lines = explode("\n", trim($contributorsOutput));
foreach ($lines as $line) {
$contributors[] = trim($line);
}
}
return [
'commit_count' => (int)$commitCount,
'branch_count' => $branchCount,
'contributors' => $contributors
];
}
private function executeGitCommand($repoDir, $command) {
$output = [];
$returnValue = 0;
$fullCommand = "cd $repoDir && git $command 2>&1";
exec($fullCommand, $output, $returnValue);
if ($returnValue !== 0) {
throw new Exception("Git command failed: " . implode("\n", $output));
}
return implode("\n", $output);
}
}
// install.php - 数据库安装脚本
function installDatabase() {
$db = Database::getInstance();
// 创建用户表
$db->query("CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role ENUM('admin', 'editor', 'viewer') NOT NULL DEFAULT 'viewer',
created_at DATETIME NOT NULL
)");
// 创建仓库表
$db->query("CREATE TABLE IF NOT EXISTS repositories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
owner_id INT NOT NULL,
created_at DATETIME NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id)
)");
// 创建文档表
$db->query("CREATE TABLE IF NOT EXISTS documents (
id INT AUTO_INCREMENT PRIMARY KEY,
repository_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
file_path VARCHAR(255) NOT NULL,
commit_hash VARCHAR(40) NOT NULL,
uploaded_by INT NOT NULL,
uploaded_at DATETIME NOT NULL,
FOREIGN KEY (repository_id) REFERENCES repositories(id),
FOREIGN KEY (uploaded_by) REFERENCES users(id)
)");
// 创建分支表
$db->query("CREATE TABLE IF NOT EXISTS branches (
id INT AUTO_INCREMENT PRIMARY KEY,
repository_id INT NOT NULL,
name VARCHAR(100) NOT NULL,
source_branch VARCHAR(100) NOT NULL,
created_by INT NOT NULL,
created_at DATETIME NOT NULL,
FOREIGN KEY (repository_id) REFERENCES repositories(id),
FOREIGN KEY (created_by) REFERENCES users(id),
UNIQUE KEY (repository_id, name)
)");
// 创建合并记录表
$db->query("CREATE TABLE IF NOT EXISTS merges (
id INT AUTO_INCREMENT PRIMARY KEY,
repository_id INT NOT NULL,
source_branch VARCHAR(100) NOT NULL,
target_branch VARCHAR(100) NOT NULL,
merged_by INT NOT NULL,
merged_at DATETIME NOT NULL,
status ENUM('success', 'failed') NOT NULL,
FOREIGN KEY (repository_id) REFERENCES repositories(id),
FOREIGN KEY (merged_by) REFERENCES users(id)
)");
// 创建仓库权限表
$db->query("CREATE TABLE IF NOT EXISTS repository_permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
repository_id INT NOT NULL,
user_id INT NOT NULL,
permission ENUM('read', 'write', 'admin') NOT NULL,
granted_at DATETIME NOT NULL,
FOREIGN KEY (repository_id) REFERENCES repositories(id),
FOREIGN KEY (user_id) REFERENCES users(id),
UNIQUE KEY (repository_id, user_id)
)");
// 创建默认管理员账户
$adminExists = $db->selectOne('users', 'role = ?', [Config::ROLE_ADMIN]);
if (!$adminExists) {
$db->insert('users', [
'username' => 'admin',
'email' => 'admin@example.com',
'password' => password_hash('admin123', PASSWORD_DEFAULT),
'role' => Config::ROLE_ADMIN,
'created_at' => date('Y-m-d H:i:s')
]);
}
return "Database installed successfully!";
}
// index.php - 主入口文件
require_once 'config.php';
require_once 'db.php';
require_once 'auth.php';
require_once 'repository.php';
// 初始化
$auth = new Auth();
$repo = new Repository($auth);
// 处理请求
$action = $_GET['action'] ?? 'home';
try {
switch ($action) {
case 'home':
// 首页
if ($auth->isLoggedIn()) {
$repositories = $repo->getRepositories();
include 'views/dashboard.php';
} else {
include 'views/home.php';
}
break;
case 'login':
// 登录
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if ($auth->login($username, $password)) {
header('Location: index.php');
exit;
} else {
$error = "Invalid username or password";
include 'views/login.php';
}
} else {
include 'views/login.php';
}
break;
case 'logout':
// 登出
$auth->logout();
header('Location: index.php');
break;
case 'register':
// 注册
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
if ($password !== $confirmPassword) {
$error = "Passwords do not match";
include 'views/register.php';
} else {
try {
$auth->register($username, $email, $password);
$success = "Registration successful. Please login.";
include 'views/login.php';
} catch (Exception $e) {
$error = $e->getMessage();
include 'views/register.php';
}
}
} else {
include 'views/register.php';
}
break;
case 'create_repo':
// 创建仓库
$auth->requirePermission('admin');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
try {
$repoId = $repo->createRepository($name, $description);
header("Location: index.php?action=repo&id=$repoId");
exit;
} catch (Exception $e) {
$error = $e->getMessage();
include 'views/create_repo.php';
}
} else {
include 'views/create_repo.php';
}
break;
case 'repo':
// 仓库详情
$repoId = $_GET['id'] ?? 0;
if (!$repoId) {
header('Location: index.php');
exit;
}
$repository = $repo->getRepository($repoId);
$documents = $repo->getDocuments($repoId);
$stats = $repo->getRepositoryStats($repoId);
include 'views/repo.php';
break;
case 'upload':
// 上传文档
$repoId = $_GET['id'] ?? 0;
if (!$repoId) {
header('Location: index.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = $_POST['message'] ?? '';
try {
$repo->addDocument($repoId, $_FILES['file'], $message);
header("Location: index.php?action=repo&id=$repoId");
exit;
} catch (Exception $e) {
$error = $e->getMessage();
$repository = $repo->getRepository($repoId);
include 'views/upload.php';
}
} else {
$repository = $repo->getRepository($repoId);
include 'views/upload.php';
}
break;
case 'history':
// 文档历史
$repoId = $_GET['repo_id'] ?? 0;
$fileName = $_GET['file'] ?? '';
if (!$repoId || !$fileName) {
header('Location: index.php');
exit;
}
$history = $repo->getDocumentHistory($repoId, $fileName);
$repository = $repo->getRepository($repoId);
include 'views/history.php';
break;
case 'version':
// 文档版本
$repoId = $_GET['repo_id'] ?? 0;
$fileName = $_GET['file'] ?? '';
$commitHash = $_GET['hash'] ?? '';
if (!$repoId || !$fileName || !$commitHash) {
header('Location: index.php');
exit;
}
$content = $repo->getDocumentVersion($repoId, $fileName, $commitHash);
$repository = $repo->getRepository($repoId);
include 'views/version.php';
break;
case 'create_branch':
// 创建分支
$repoId = $_GET['repo_id'] ?? 0;
if (!$repoId) {
header('Location: index.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$branchName = $_POST['name'] ?? '';
$sourceBranch = $_POST['source'] ?? 'main';
try {
$repo->createBranch($repoId, $branchName, $sourceBranch);
header("Location: index.php?action=repo&id=$repoId");
exit;
} catch (Exception $e) {
$error = $e->getMessage();
$repository = $repo->getRepository($repoId);
include 'views/create_branch.php';
}
} else {
$repository = $repo->getRepository($repoId);
include 'views/create_branch.php';
}
break;
case 'merge_branch':
// 合并分支
$repoId = $_GET['repo_id'] ?? 0;
if (!$repoId) {
header('Location: index.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sourceBranch = $_POST['source'] ?? '';
$targetBranch = $_POST['target'] ?? 'main';
try {
$result = $repo->mergeBranch($repoId, $sourceBranch, $targetBranch);
header("Location: index.php?action=repo&id=$repoId");
exit;
} catch (Exception $e) {
$error = $e->getMessage();
$repository = $repo->getRepository($repoId);
include 'views/merge_branch.php';
}
} else {
$repository = $repo->getRepository($repoId);
include 'views/merge_branch.php';
}
break;
case 'install':
// 安装数据库
echo installDatabase();
break;
default:
// 默认首页
include 'views/home.php';
break;
}
} catch (Exception $e) {
$error = $e->getMessage();
include 'views/error.php';
}
?>