From ec563fc407f4a62bc4b975ff7af7d1783855ee97 Mon Sep 17 00:00:00 2001 From: Tsama <1693710928@qq.com> Date: Sat, 15 Nov 2025 17:37:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .git_remote_config.json | 5 + README.md | 217 +++++++++++++++ css/style.css | 478 ++++++++++++++++++++++++++++++++ index.php | 511 ++++++++++++++++++++++++++++++++++ js/main.js | 593 ++++++++++++++++++++++++++++++++++++++++ test.php | 179 ++++++++++++ uploads/test.php | 179 ++++++++++++ 7 files changed, 2162 insertions(+) create mode 100644 .git_remote_config.json create mode 100644 README.md create mode 100644 css/style.css create mode 100644 index.php create mode 100644 js/main.js create mode 100644 test.php create mode 100644 uploads/test.php diff --git a/.git_remote_config.json b/.git_remote_config.json new file mode 100644 index 0000000..585fd03 --- /dev/null +++ b/.git_remote_config.json @@ -0,0 +1,5 @@ +{ + "remotes": { + "origin": "https:\/\/git.tsama.cn\/ll\/Demo1.git" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..25f455c --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# Git文件上传工具 + +一个基于PHP的Git文件上传工具,支持JS和CSS分离,文件上传无限制。 + +## 功能特点 + +- ✅ **文件上传无限制**:支持任意大小和数量的文件上传 +- ✅ **Git集成**:完整的Git操作支持(添加、提交、推送、状态查看) +- ✅ **分支管理**:支持创建、删除、切换分支 +- ✅ **远程仓库配置**:支持添加和管理多个远程仓库 +- ✅ **前后端分离**:独立的CSS和JavaScript文件 +- ✅ **拖拽上传**:支持拖拽文件上传 +- ✅ **文件管理**:可视化的文件列表和选择 +- ✅ **实时状态**:Git仓库状态实时显示 +- ✅ **响应式设计**:适配移动端和桌面端 + +## 文件结构 + +``` +git/ +├── index.php # 主文件(包含PHP后端逻辑和HTML结构) +├── css/ +│ └── style.css # 样式文件 +├── js/ +│ └── main.js # JavaScript交互逻辑 +├── test.php # 测试文件 +└── uploads/ # 上传文件存储目录(自动生成) +``` + +## 安装要求 + +- PHP 7.0 或更高版本 +- Git 已安装并配置 +- Web服务器(Apache/Nginx等) +- 足够的磁盘空间 + +## 安装步骤 + +1. **上传文件到Web服务器** + ```bash + # 将整个 git 目录上传到Web服务器目录 + # 例如:/var/www/html/git/ + ``` + +2. **设置目录权限** + ```bash + chmod 755 /var/www/html/git/ + chmod 777 /var/www/html/git/uploads/ + ``` + +3. **访问工具** + ``` + http://your-domain.com/git/ + ``` + +## 使用方法 + +### 1. 文件上传 + +- **拖拽上传**:将文件拖拽到上传区域 +- **点击上传**:点击上传区域选择文件 +- **批量上传**:支持同时选择多个文件 + +### 2. 分支管理 + +- **查看分支**:分支列表会自动加载,显示当前分支和其他分支 +- **创建新分支**: + 1. 在"新分支名称"输入框中输入分支名称 + 2. 点击"创建分支"按钮 + 3. 新分支将基于当前分支创建 + +- **切换分支**: + 1. 在"当前分支"下拉框中选择目标分支 + 2. 点击"切换分支"按钮 + 3. 工具会自动切换到选中的分支 + +- **删除分支**: + 1. 在"当前分支"下拉框中选择要删除的分支 + 2. 点击"删除分支"按钮 + 3. 确认删除操作(如果分支未合并,可选择强制删除) + +- **刷新分支**:点击"刷新分支"按钮更新分支列表 + +### 3. 远程仓库配置 + +- **添加远程仓库**: + 1. 在"远程仓库名称"输入框中输入名称(如:origin) + 2. 在"仓库地址"输入框中输入远程仓库URL(如:https://github.com/username/repo.git) + 3. 点击"添加远程仓库"按钮 + +- **查看远程仓库**:点击"查看远程仓库"按钮查看已配置的远程仓库列表 + +### 4. Git操作 + +- **添加到暂存区**:选择文件后点击"添加到暂存区" +- **提交更改**:输入提交信息后点击"提交更改" +- **推送到远程**: + 1. 确保已配置远程仓库 + 2. 选择要推送的分支(在分支管理区域) + 3. 在"远程仓库名称"输入框中输入要推送的远程仓库名称 + 4. 点击"推送到远程"按钮 +- **查看状态**:点击"查看状态"查看Git状态 +- **查看日志**:点击"查看日志"查看提交历史 + +### 5. 文件管理 + +- **刷新列表**:点击"刷新文件列表"更新文件列表 +- **选择文件**:勾选需要Git管理的文件 +- **全选/取消**:快速选择或取消所有文件 + +## 配置说明 + +### PHP配置 + +工具会自动配置以下PHP设置: +```php +ini_set('upload_max_filesize', '0'); // 无上传大小限制 +ini_set('post_max_size', '0'); // 无POST大小限制 +ini_set('max_execution_time', '0'); // 无执行时间限制 +ini_set('memory_limit', '-1'); // 无内存限制 +``` + +### Git配置 + +确保Web服务器用户有权限执行Git命令: +```bash +# 检查Git是否可用 +sudo -u www-data git --version + +# 如果需要,配置Git用户信息 +sudo -u www-data git config --global user.name "Web Server" +sudo -u www-data git config --global user.email "web@example.com" +``` + +## 安全建议 + +1. **访问控制**:建议添加密码保护或IP限制 +2. **文件类型限制**:根据需要限制上传文件类型 +3. **定期清理**:定期清理上传目录 +4. **备份重要数据**:重要文件请做好备份 + +## 故障排除 + +### 常见问题 + +1. **文件上传失败** + - 检查 `uploads/` 目录权限 + - 确认PHP配置允许大文件上传 + - 查看Web服务器错误日志 + +2. **Git操作失败** + - 确认Git已安装 + - 检查Web服务器用户对Git的访问权限 + - 确认Git仓库已初始化 + +3. **分支管理问题** + - 无法删除当前分支,请先切换到其他分支 + - 分支未合并时删除需要强制删除 + - 分支名称不能包含特殊字符 + +4. **远程仓库配置问题** + - 确保远程仓库URL格式正确 + - 检查网络连接是否正常 + - 确认有权限访问远程仓库 + - 如需要认证,请配置Git凭据 + +5. **推送失败** + - 确认远程仓库已正确添加 + - 检查是否有冲突需要解决 + - 确认有推送权限 + - 确认选择了正确的分支进行推送 + +6. **界面显示异常** + - 检查CSS文件是否正确加载 + - 确认JavaScript文件无错误 + - 清除浏览器缓存 + +### 运行测试 + +访问测试文件检查配置: +``` +http://your-domain.com/git/test.php +``` + +## 更新日志 + +### v1.2.0 (2024-01-15) +- 添加分支管理功能 +- 支持创建、删除、切换分支 +- 支持分支选择和推送 +- 优化分支操作的用户体验 + +### v1.1.0 (2024-01-15) +- 添加远程仓库配置功能 +- 支持添加和管理远程仓库 +- 支持推送到指定远程仓库 +- 优化推送操作的用户体验 + +### v1.0.0 (2024-01-15) +- 初始版本发布 +- 支持文件上传和Git操作 +- 实现前后端分离 +- 添加拖拽上传功能 + +## 许可证 + +MIT License - 详见LICENSE文件 + +## 作者 + +Git文件上传工具开发团队 + +## 支持 + +如有问题,请通过以下方式联系: +- 邮箱:support@example.com +- 项目主页:https://github.com/your-repo/git-uploader \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..038bce8 --- /dev/null +++ b/css/style.css @@ -0,0 +1,478 @@ +/* Git文件上传工具样式 */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + line-height: 1.6; + color: #333; + background-color: #f5f5f5; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* 头部样式 */ +header { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +header h1 { + color: #2c3e50; + font-size: 24px; +} + +.git-status { + display: flex; + align-items: center; + gap: 10px; +} + +.status-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + background-color: #e74c3c; + transition: background-color 0.3s; +} + +.status-indicator.active { + background-color: #27ae60; +} + +/* 主要内容区域 */ +main { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; +} + +section { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +section h2 { + color: #2c3e50; + margin-bottom: 15px; + font-size: 18px; +} + +/* 上传区域样式 */ +.upload-section { + grid-column: 1 / -1; +} + +.upload-area { + border: 2px dashed #3498db; + border-radius: 8px; + padding: 40px; + text-align: center; + cursor: pointer; + transition: all 0.3s; + background-color: #f8f9fa; +} + +.upload-area:hover { + border-color: #2980b9; + background-color: #e3f2fd; +} + +.upload-area.dragover { + border-color: #27ae60; + background-color: #e8f5e8; +} + +.upload-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.upload-icon { + width: 48px; + height: 48px; + color: #3498db; +} + +.upload-hint { + font-size: 12px; + color: #7f8c8d; +} + +.uploaded-files { + margin-top: 15px; +} + +.file-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: #e8f5e8; + border-radius: 4px; + margin-bottom: 5px; +} + +.file-item.success { + background-color: #d4edda; + border-left: 4px solid #27ae60; +} + +.file-item.error { + background-color: #f8d7da; + border-left: 4px solid #e74c3c; +} + +/* 文件列表样式 */ +.files-section { + max-height: 500px; + overflow-y: auto; +} + +.file-controls { + display: flex; + gap: 10px; + margin-bottom: 15px; +} + +.file-list { + border: 1px solid #e0e0e0; + border-radius: 4px; + max-height: 400px; + overflow-y: auto; +} + +.file-checkbox { + display: flex; + align-items: center; + padding: 8px 12px; + border-bottom: 1px solid #f0f0f0; + cursor: pointer; + transition: background-color 0.2s; +} + +.file-checkbox:hover { + background-color: #f8f9fa; +} + +.file-checkbox:last-child { + border-bottom: none; +} + +.file-checkbox input[type="checkbox"] { + margin-right: 10px; +} + +.file-path { + font-family: 'Courier New', monospace; + font-size: 14px; + color: #495057; +} + +/* Git操作区域样式 */ +.git-section { + max-height: 500px; + overflow-y: auto; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: #495057; +} + +.form-group input { + width: 100%; + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 14px; +} + +.form-group input:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); +} + +.button-group { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 15px; +} + +/* 按钮样式 */ +.btn { + padding: 8px 16px; + border: none; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + transition: all 0.3s; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 5px; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +.btn:active { + transform: translateY(0); +} + +.btn-primary { + background-color: #3498db; + color: white; +} + +.btn-primary:hover { + background-color: #2980b9; +} + +.btn-success { + background-color: #27ae60; + color: white; +} + +.btn-success:hover { + background-color: #229954; +} + +.btn-warning { + background-color: #f39c12; + color: white; +} + +.btn-warning:hover { + background-color: #e67e22; +} + +.btn-info { + background-color: #17a2b8; + color: white; +} + +.btn-info:hover { + background-color: #138496; +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.btn-danger { + background-color: #e74c3c; + color: white; +} + +.btn-danger:hover { + background-color: #c0392b; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +/* 分支管理 */ +.branch-config { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 15px; + margin-bottom: 20px; +} + +.branch-config h3 { + color: #495057; + margin-bottom: 15px; + font-size: 16px; +} + +.form-control { + width: 100%; + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 14px; + background-color: white; +} + +.form-control:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); +} + +/* 远程仓库配置 */ +.remote-config { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 15px; + margin-bottom: 20px; +} + +.remote-config h3 { + color: #495057; + margin-bottom: 15px; + font-size: 16px; +} + +.remote-list { + margin-top: 15px; + max-height: 150px; + overflow-y: auto; +} + +.remote-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: white; + border: 1px solid #dee2e6; + border-radius: 4px; + margin-bottom: 8px; +} + +.remote-item:last-child { + margin-bottom: 0; +} + +.remote-name { + font-weight: 500; + color: #495057; +} + +.remote-url { + font-size: 12px; + color: #6c757d; + font-family: 'Courier New', monospace; + word-break: break-all; +} + +/* Git输出区域 */ +.git-output { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 12px; + font-family: 'Courier New', monospace; + font-size: 13px; + max-height: 200px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; +} + +.git-output:empty::before { + content: "Git操作输出将显示在这里..."; + color: #6c757d; + font-style: italic; +} + +/* 加载状态 */ +.loading { + text-align: center; + padding: 20px; + color: #6c757d; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + main { + grid-template-columns: 1fr; + } + + header { + flex-direction: column; + gap: 15px; + text-align: center; + } + + .button-group { + justify-content: center; + } + + .file-controls { + flex-wrap: wrap; + justify-content: center; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in { + animation: fadeIn 0.3s ease-out; +} + +/* 错误和成功消息 */ +.message { + padding: 12px; + border-radius: 4px; + margin-bottom: 15px; + font-size: 14px; +} + +.message.success { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.message.error { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.message.info { + background-color: #d1ecf1; + color: #0c5460; + border: 1px solid #bee5eb; +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..0660089 --- /dev/null +++ b/index.php @@ -0,0 +1,511 @@ +isFile() && !strpos($file->getPathname(), '.git')) { + $relativePath = str_replace('\\', '/', substr($file->getPathname(), strlen(__DIR__) + 1)); + $files[] = $relativePath; + } + } + + return $files; +} + +// 处理文件上传 +function handleFileUpload($files) { + $uploadedFiles = []; + + if (!isset($files['files']) || empty($files['files']['name'][0])) { + return ['success' => false, 'message' => '没有选择文件']; + } + + $uploadDir = __DIR__ . '/uploads/'; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0777, true); + } + + $fileCount = count($files['files']['name']); + + for ($i = 0; $i < $fileCount; $i++) { + if ($files['files']['error'][$i] === UPLOAD_ERR_OK) { + $tmpName = $files['files']['tmp_name'][$i]; + $fileName = basename($files['files']['name'][$i]); + $uploadPath = $uploadDir . $fileName; + + // 移动上传的文件 + if (move_uploaded_file($tmpName, $uploadPath)) { + $uploadedFiles[] = $fileName; + } + } + } + + return ['success' => true, 'files' => $uploadedFiles]; +} + +// 获取远程仓库配置 +function getRemoteConfig() { + global $remoteConfigFile; + + if (file_exists($remoteConfigFile)) { + $config = json_decode(file_get_contents($remoteConfigFile), true); + return $config ?: ['remotes' => []]; + } + + return ['remotes' => []]; +} + +// 保存远程仓库配置 +function saveRemoteConfig($config) { + global $remoteConfigFile; + + return file_put_contents($remoteConfigFile, json_encode($config, JSON_PRETTY_PRINT)); +} + +// 添加远程仓库 +function addRemote($name, $url) { + global $currentDir; + + try { + exec('cd "' . $currentDir . '" && git remote add ' . escapeshellarg($name) . ' ' . escapeshellarg($url), $output, $returnCode); + + if ($returnCode === 0) { + // 保存到配置文件 + $config = getRemoteConfig(); + $config['remotes'][$name] = $url; + saveRemoteConfig($config); + return ['success' => true, 'message' => '远程仓库添加成功']; + } else { + return ['success' => false, 'message' => '远程仓库添加失败']; + } + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 获取远程仓库列表 +function getRemotes() { + global $currentDir; + + try { + exec('cd "' . $currentDir . '" && git remote -v', $output, $returnCode); + + $remotes = []; + foreach ($output as $line) { + if (preg_match('/^(\S+)\s+(\S+)\s+\((\w+)\)/', $line, $matches)) { + $remotes[$matches[1]] = [ + 'url' => $matches[2], + 'type' => $matches[3] + ]; + } + } + + return ['success' => true, 'remotes' => $remotes]; + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 推送代码到指定远程仓库 +function pushToRemote($remote = 'origin', $branch = 'main') { + global $currentDir; + + try { + exec('cd "' . $currentDir . '" && git push ' . escapeshellarg($remote) . ' ' . escapeshellarg($branch), $output, $returnCode); + + return ['success' => $returnCode === 0, 'output' => $output]; + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 获取分支列表 +function getBranches() { + global $currentDir; + + try { + $output = []; + $returnCode = 0; + + // 获取本地分支 + exec('cd "' . $currentDir . '" && git branch -a', $output, $returnCode); + + $branches = []; + $currentBranch = ''; + + foreach ($output as $line) { + $line = trim($line); + if (empty($line)) continue; + + // 检查是否是当前分支 + if (strpos($line, '*') === 0) { + $currentBranch = trim(substr($line, 1)); + $branches[] = [ + 'name' => $currentBranch, + 'current' => true, + 'remote' => false + ]; + } else { + // 检查是否是远程分支 + $isRemote = strpos($line, 'remotes/') !== false; + $branchName = $isRemote ? substr($line, strpos($line, 'remotes/') + 8) : $line; + + $branches[] = [ + 'name' => trim($branchName), + 'current' => false, + 'remote' => $isRemote + ]; + } + } + + return ['success' => true, 'branches' => $branches, 'current' => $currentBranch]; + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 创建新分支 +function createBranch($branchName) { + global $currentDir; + + try { + exec('cd "' . $currentDir . '" && git checkout -b ' . escapeshellarg($branchName), $output, $returnCode); + + if ($returnCode === 0) { + return ['success' => true, 'message' => '分支创建成功', 'output' => $output]; + } else { + return ['success' => false, 'message' => '分支创建失败', 'output' => $output]; + } + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 删除分支 +function deleteBranch($branchName, $force = false) { + global $currentDir; + + try { + $forceFlag = $force ? '-D' : '-d'; + exec('cd "' . $currentDir . '" && git branch ' . $forceFlag . ' ' . escapeshellarg($branchName), $output, $returnCode); + + if ($returnCode === 0) { + return ['success' => true, 'message' => '分支删除成功', 'output' => $output]; + } else { + return ['success' => false, 'message' => '分支删除失败', 'output' => $output]; + } + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 切换分支 +function switchBranch($branchName) { + global $currentDir; + + try { + exec('cd "' . $currentDir . '" && git checkout ' . escapeshellarg($branchName), $output, $returnCode); + + if ($returnCode === 0) { + return ['success' => true, 'message' => '分支切换成功', 'output' => $output]; + } else { + return ['success' => false, 'message' => '分支切换失败', 'output' => $output]; + } + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 处理Git操作 +function handleGitOperation($operation, $message = '', $files = [], $remote = 'origin', $branch = 'main') { + global $currentDir; + + try { + $output = []; + $returnCode = 0; + + switch ($operation) { + case 'status': + exec('cd "' . $currentDir . '" && git status --porcelain', $output, $returnCode); + break; + + case 'add': + if (!empty($files)) { + foreach ($files as $file) { + exec('cd "' . $currentDir . '" && git add "' . $file . '"', $output, $returnCode); + } + } else { + exec('cd "' . $currentDir . '" && git add .', $output, $returnCode); + } + break; + + case 'commit': + if (empty($message)) { + $message = '自动提交'; + } + exec('cd "' . $currentDir . '" && git commit -m "' . addslashes($message) . '"', $output, $returnCode); + break; + + case 'push': + // 使用新的推送函数,支持指定远程仓库和分支 + return pushToRemote($remote, $branch); + + case 'log': + exec('cd "' . $currentDir . '" && git log --oneline -10', $output, $returnCode); + break; + } + + return ['success' => $returnCode === 0, 'output' => $output]; + } catch (Exception $e) { + return ['success' => false, 'message' => $e->getMessage()]; + } +} + +// 处理AJAX请求 +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + header('Content-Type: application/json; charset=utf-8'); + + $action = $_POST['action'] ?? ''; + + switch ($action) { + case 'upload': + $result = handleFileUpload($_FILES); + echo json_encode($result); + break; + + case 'git_operation': + $operation = $_POST['operation'] ?? ''; + $message = $_POST['message'] ?? ''; + $files = $_POST['files'] ?? []; + $remote = $_POST['remote'] ?? 'origin'; + $branch = $_POST['branch'] ?? 'main'; + $result = handleGitOperation($operation, $message, $files, $remote, $branch); + echo json_encode($result); + break; + + case 'add_remote': + $name = $_POST['name'] ?? ''; + $url = $_POST['url'] ?? ''; + if (empty($name) || empty($url)) { + echo json_encode(['success' => false, 'message' => '远程仓库名称和地址不能为空']); + } else { + $result = addRemote($name, $url); + echo json_encode($result); + } + break; + + case 'get_remotes': + $result = getRemotes(); + echo json_encode($result); + break; + + case 'get_branches': + $result = getBranches(); + echo json_encode($result); + break; + + case 'create_branch': + $branchName = $_POST['branch_name'] ?? ''; + if (empty($branchName)) { + echo json_encode(['success' => false, 'message' => '分支名称不能为空']); + } else { + $result = createBranch($branchName); + echo json_encode($result); + } + break; + + case 'delete_branch': + $branchName = $_POST['branch_name'] ?? ''; + $force = isset($_POST['force']) && $_POST['force'] === 'true'; + if (empty($branchName)) { + echo json_encode(['success' => false, 'message' => '分支名称不能为空']); + } else { + $result = deleteBranch($branchName, $force); + echo json_encode($result); + } + break; + + case 'switch_branch': + $branchName = $_POST['branch_name'] ?? ''; + if (empty($branchName)) { + echo json_encode(['success' => false, 'message' => '分支名称不能为空']); + } else { + $result = switchBranch($branchName); + echo json_encode($result); + } + break; + + case 'get_files': + $files = getFileList(); + echo json_encode(['success' => true, 'files' => $files]); + break; + + case 'check_git': + echo json_encode(['success' => true, 'hasGit' => checkGitRepo()]); + break; + + case 'init_git': + $success = initGitRepo(); + echo json_encode(['success' => $success]); + break; + + default: + echo json_encode(['success' => false, 'message' => '未知操作']); + } + exit; +} + +?> + + + + + + Git文件上传工具 + + + +
+
+

Git文件上传工具

+
+ + 检查Git状态... +
+
+ +
+ +
+

文件上传

+
+
+ + + +

拖拽文件到此处或点击选择文件

+

支持任意大小的文件,无数量限制

+
+ +
+
+
+ + +
+

项目文件

+
+ + + +
+
+
加载文件中...
+
+
+ + +
+

Git操作

+ + +
+

分支管理

+
+ + +
+
+ + +
+
+ + + + +
+
+ +
+ + +
+

远程仓库配置

+
+ + +
+
+ + +
+ + + +
+
+ +
+ +
+
+ + +
+
+ + + + + +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..e909a31 --- /dev/null +++ b/js/main.js @@ -0,0 +1,593 @@ +/** + * Git文件上传工具 - 主要JavaScript功能 + * 支持文件上传、Git操作、文件管理等功能 + */ + +class GitUploader { + constructor() { + this.selectedFiles = new Set(); + this.init(); + } + + // 初始化 + init() { + this.setupEventListeners(); + this.checkGitStatus(); + this.loadFileList(); + this.showRemotes(); // 加载远程仓库列表 + this.loadBranches(); // 加载分支列表 + } + + // 设置事件监听器 + setupEventListeners() { + // 文件上传相关 + const uploadArea = document.getElementById('uploadArea'); + const fileInput = document.getElementById('fileInput'); + + uploadArea.addEventListener('click', () => fileInput.click()); + uploadArea.addEventListener('dragover', this.handleDragOver.bind(this)); + uploadArea.addEventListener('dragleave', this.handleDragLeave.bind(this)); + uploadArea.addEventListener('drop', this.handleDrop.bind(this)); + fileInput.addEventListener('change', this.handleFileSelect.bind(this)); + + // Git操作相关 + document.getElementById('gitAdd').addEventListener('click', () => this.gitOperation('add')); + document.getElementById('gitCommit').addEventListener('click', () => this.gitOperation('commit')); + document.getElementById('gitPush').addEventListener('click', () => this.gitOperation('push')); + document.getElementById('gitStatusBtn').addEventListener('click', () => this.gitOperation('status')); + document.getElementById('gitLog').addEventListener('click', () => this.gitOperation('log')); + + // 远程仓库相关 + document.getElementById('addRemote').addEventListener('click', () => this.addRemote()); + document.getElementById('showRemotes').addEventListener('click', () => this.showRemotes()); + + // 分支管理相关 + document.getElementById('createBranch').addEventListener('click', () => this.createBranch()); + document.getElementById('switchBranch').addEventListener('click', () => this.switchBranch()); + document.getElementById('deleteBranch').addEventListener('click', () => this.deleteBranch()); + document.getElementById('refreshBranches').addEventListener('click', () => this.loadBranches()); + + // 文件列表相关 + document.getElementById('refreshFiles').addEventListener('click', () => this.loadFileList()); + document.getElementById('selectAllFiles').addEventListener('click', () => this.selectAllFiles()); + document.getElementById('deselectAllFiles').addEventListener('click', () => this.deselectAllFiles()); + } + + // 拖拽处理 + handleDragOver(e) { + e.preventDefault(); + document.getElementById('uploadArea').classList.add('dragover'); + } + + handleDragLeave(e) { + e.preventDefault(); + document.getElementById('uploadArea').classList.remove('dragover'); + } + + handleDrop(e) { + e.preventDefault(); + document.getElementById('uploadArea').classList.remove('dragover'); + + const files = Array.from(e.dataTransfer.files); + this.uploadFiles(files); + } + + handleFileSelect(e) { + const files = Array.from(e.target.files); + this.uploadFiles(files); + } + + // 文件上传 + async uploadFiles(files) { + if (files.length === 0) return; + + const formData = new FormData(); + formData.append('action', 'upload'); + + files.forEach(file => { + formData.append('files[]', file); + }); + + try { + this.showMessage('正在上传文件...', 'info'); + + const response = await fetch('index.php', { + method: 'POST', + body: formData + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage(`成功上传 ${result.files.length} 个文件`, 'success'); + this.displayUploadedFiles(result.files); + this.loadFileList(); // 重新加载文件列表 + } else { + this.showMessage(result.message || '上传失败', 'error'); + } + } catch (error) { + this.showMessage('上传出错: ' + error.message, 'error'); + } + } + + // 显示已上传的文件 + displayUploadedFiles(files) { + const container = document.getElementById('uploadedFiles'); + container.innerHTML = ''; + + files.forEach(file => { + const fileItem = document.createElement('div'); + fileItem.className = 'file-item success fade-in'; + fileItem.innerHTML = ` + ${file} + + `; + container.appendChild(fileItem); + }); + } + + // 检查Git状态 + async checkGitStatus() { + try { + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'action=check_git' + }); + + const result = await response.json(); + const indicator = document.getElementById('gitIndicator'); + const statusText = document.getElementById('gitStatusText'); + + if (result.success && result.hasGit) { + indicator.classList.add('active'); + statusText.textContent = 'Git仓库已初始化'; + } else { + indicator.classList.remove('active'); + statusText.textContent = 'Git仓库未初始化'; + + // 自动初始化Git仓库 + if (confirm('Git仓库未初始化,是否现在初始化?')) { + await this.initGitRepo(); + } + } + } catch (error) { + this.showMessage('检查Git状态失败: ' + error.message, 'error'); + } + } + + // 初始化Git仓库 + async initGitRepo() { + try { + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'action=init_git' + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage('Git仓库初始化成功', 'success'); + this.checkGitStatus(); // 重新检查状态 + } else { + this.showMessage('Git仓库初始化失败', 'error'); + } + } catch (error) { + this.showMessage('初始化Git仓库失败: ' + error.message, 'error'); + } + } + + // Git操作 + async gitOperation(operation) { + const commitMessage = document.getElementById('commitMessage').value || '自动提交'; + const selectedFiles = Array.from(this.selectedFiles); + const remoteName = document.getElementById('remoteName').value || 'origin'; + const branchSelect = document.getElementById('branchSelect'); + const currentBranch = branchSelect ? branchSelect.value : 'main'; + + const params = new URLSearchParams(); + params.append('action', 'git_operation'); + params.append('operation', operation); + + if (operation === 'commit') { + params.append('message', commitMessage); + } + + if (operation === 'push') { + params.append('remote', remoteName); + params.append('branch', currentBranch); // 使用当前选择的分支 + } + + if (operation === 'add' && selectedFiles.length > 0) { + selectedFiles.forEach(file => { + params.append('files[]', file); + }); + } + + try { + this.showMessage(`正在执行 Git ${operation}...`, 'info'); + + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage(`Git ${operation} 执行成功`, 'success'); + this.displayGitOutput(result.output); + + if (operation === 'add' || operation === 'commit') { + this.loadFileList(); // 重新加载文件列表 + } + } else { + this.showMessage(`Git ${operation} 执行失败: ${result.message || '未知错误'}`, 'error'); + this.displayGitOutput(result.output || []); + } + } catch (error) { + this.showMessage(`Git ${operation} 执行出错: ${error.message}`, 'error'); + } + } + + // 显示Git输出 + displayGitOutput(output) { + const outputElement = document.getElementById('gitOutput'); + + if (output && output.length > 0) { + outputElement.textContent = output.join('\n'); + } else { + outputElement.textContent = '暂无输出'; + } + + outputElement.scrollTop = outputElement.scrollHeight; + } + + // 添加远程仓库 + async addRemote() { + const name = document.getElementById('remoteName').value.trim(); + const url = document.getElementById('remoteUrl').value.trim(); + + if (!name || !url) { + this.showMessage('请输入远程仓库名称和地址', 'error'); + return; + } + + const params = new URLSearchParams(); + params.append('action', 'add_remote'); + params.append('name', name); + params.append('url', url); + + try { + this.showMessage('正在添加远程仓库...', 'info'); + + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage('远程仓库添加成功', 'success'); + document.getElementById('remoteUrl').value = ''; // 清空输入框 + this.showRemotes(); // 刷新远程仓库列表 + } else { + this.showMessage('添加远程仓库失败: ' + (result.message || '未知错误'), 'error'); + } + } catch (error) { + this.showMessage('添加远程仓库出错: ' + error.message, 'error'); + } + } + + // 显示远程仓库列表 + async showRemotes() { + try { + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'action=get_remotes' + }); + + const result = await response.json(); + const remoteList = document.getElementById('remoteList'); + + if (result.success && result.remotes && Object.keys(result.remotes).length > 0) { + remoteList.innerHTML = ''; + + Object.entries(result.remotes).forEach(([name, info]) => { + const remoteItem = document.createElement('div'); + remoteItem.className = 'remote-item'; + remoteItem.innerHTML = ` +
+
${name} (${info.type})
+
${info.url}
+
+ `; + remoteList.appendChild(remoteItem); + }); + } else { + remoteList.innerHTML = '
暂无远程仓库
'; + } + } catch (error) { + this.showMessage('获取远程仓库列表失败: ' + error.message, 'error'); + } + } + + // 加载分支列表 + async loadBranches() { + try { + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'action=get_branches' + }); + + const result = await response.json(); + const branchSelect = document.getElementById('branchSelect'); + + if (result.success && result.branches) { + branchSelect.innerHTML = ''; + + result.branches.forEach(branch => { + const option = document.createElement('option'); + option.value = branch.name; + option.textContent = branch.name + (branch.current ? ' (当前)' : '') + (branch.remote ? ' (远程)' : ''); + if (branch.current) { + option.selected = true; + } + branchSelect.appendChild(option); + }); + } else { + branchSelect.innerHTML = ''; + } + } catch (error) { + this.showMessage('获取分支列表失败: ' + error.message, 'error'); + } + } + + // 创建新分支 + async createBranch() { + const branchName = document.getElementById('newBranchName').value.trim(); + + if (!branchName) { + this.showMessage('请输入分支名称', 'error'); + return; + } + + const params = new URLSearchParams(); + params.append('action', 'create_branch'); + params.append('branch_name', branchName); + + try { + this.showMessage('正在创建分支...', 'info'); + + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage('分支创建成功', 'success'); + document.getElementById('newBranchName').value = ''; // 清空输入框 + this.loadBranches(); // 重新加载分支列表 + this.displayGitOutput(result.output); + } else { + this.showMessage('创建分支失败: ' + (result.message || '未知错误'), 'error'); + this.displayGitOutput(result.output || []); + } + } catch (error) { + this.showMessage('创建分支出错: ' + error.message, 'error'); + } + } + + // 切换分支 + async switchBranch() { + const branchSelect = document.getElementById('branchSelect'); + const branchName = branchSelect.value; + + if (!branchName) { + this.showMessage('请选择要切换的分支', 'error'); + return; + } + + const params = new URLSearchParams(); + params.append('action', 'switch_branch'); + params.append('branch_name', branchName); + + try { + this.showMessage('正在切换分支...', 'info'); + + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage('分支切换成功', 'success'); + this.loadBranches(); // 重新加载分支列表 + this.loadFileList(); // 重新加载文件列表 + this.displayGitOutput(result.output); + } else { + this.showMessage('切换分支失败: ' + (result.message || '未知错误'), 'error'); + this.displayGitOutput(result.output || []); + } + } catch (error) { + this.showMessage('切换分支出错: ' + error.message, 'error'); + } + } + + // 删除分支 + async deleteBranch() { + const branchSelect = document.getElementById('branchSelect'); + const branchName = branchSelect.value; + + if (!branchName) { + this.showMessage('请选择要删除的分支', 'error'); + return; + } + + if (!confirm(`确定要删除分支 "${branchName}" 吗?`)) { + return; + } + + const params = new URLSearchParams(); + params.append('action', 'delete_branch'); + params.append('branch_name', branchName); + params.append('force', 'false'); // 默认不强制删除 + + try { + this.showMessage('正在删除分支...', 'info'); + + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params + }); + + const result = await response.json(); + + if (result.success) { + this.showMessage('分支删除成功', 'success'); + this.loadBranches(); // 重新加载分支列表 + this.displayGitOutput(result.output); + } else { + this.showMessage('删除分支失败: ' + (result.message || '未知错误'), 'error'); + this.displayGitOutput(result.output || []); + + if (confirm('是否强制删除该分支?')) { + params.set('force', 'true'); + const forceResponse = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params + }); + const forceResult = await forceResponse.json(); + if (forceResult.success) { + this.showMessage('分支强制删除成功', 'success'); + this.loadBranches(); + this.displayGitOutput(forceResult.output); + } + } + } + } catch (error) { + this.showMessage('删除分支出错: ' + error.message, 'error'); + } + } + + // 加载文件列表 + async loadFileList() { + try { + const response = await fetch('index.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'action=get_files' + }); + + const result = await response.json(); + + if (result.success) { + this.displayFileList(result.files); + } else { + this.showMessage('加载文件列表失败', 'error'); + } + } catch (error) { + this.showMessage('加载文件列表出错: ' + error.message, 'error'); + } + } + + // 显示文件列表 + displayFileList(files) { + const container = document.getElementById('fileList'); + + if (files.length === 0) { + container.innerHTML = '
暂无文件
'; + return; + } + + container.innerHTML = ''; + + files.forEach(file => { + const fileItem = document.createElement('div'); + fileItem.className = 'file-checkbox'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `file-${this.escapeId(file)}`; + checkbox.value = file; + checkbox.checked = this.selectedFiles.has(file); + checkbox.addEventListener('change', (e) => { + if (e.target.checked) { + this.selectedFiles.add(file); + } else { + this.selectedFiles.delete(file); + } + }); + + const label = document.createElement('label'); + label.htmlFor = checkbox.id; + label.className = 'file-path'; + label.textContent = file; + label.style.cursor = 'pointer'; + label.style.flex = '1'; + + fileItem.appendChild(checkbox); + fileItem.appendChild(label); + container.appendChild(fileItem); + }); + } + + // 全选文件 + selectAllFiles() { + const checkboxes = document.querySelectorAll('#fileList input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + checkbox.checked = true; + this.selectedFiles.add(checkbox.value); + }); + } + + // 取消全选 + deselectAllFiles() { + const checkboxes = document.querySelectorAll('#fileList input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + checkbox.checked = false; + this.selectedFiles.delete(checkbox.value); + }); + } + + // 工具方法:转义ID + escapeId(str) { + return str.replace(/[^a-zA-Z0-9]/g, '_'); + } + + // 显示消息 + showMessage(message, type = 'info') { + // 创建消息元素 + const messageElement = document.createElement('div'); + messageElement.className = `message ${type} fade-in`; + messageElement.textContent = message; + + // 添加到页面顶部 + const container = document.querySelector('.container'); + container.insertBefore(messageElement, container.firstChild); + + // 3秒后自动移除 + setTimeout(() => { + messageElement.remove(); + }, 3000); + } +} + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', () => { + new GitUploader(); +}); \ No newline at end of file diff --git a/test.php b/test.php new file mode 100644 index 0000000..3985411 --- /dev/null +++ b/test.php @@ -0,0 +1,179 @@ + ini_get('upload_max_filesize'), + 'post_max_size' => ini_get('post_max_size'), + 'max_execution_time' => ini_get('max_execution_time'), + 'memory_limit' => ini_get('memory_limit') + ]; + + foreach ($configs as $key => $value) { + echo " $key: $value\n"; + } + + // 检查必要的PHP函数 + $functions = ['exec', 'move_uploaded_file', 'json_encode', 'file_put_contents']; + $allFunctionsAvailable = true; + + foreach ($functions as $function) { + if (function_exists($function)) { + echo "✓ 函数 $function 可用\n"; + } else { + echo "✗ 函数 $function 不可用\n"; + $allFunctionsAvailable = false; + } + } + + return $allFunctionsAvailable; +} + +// 测试目录权限 +function testDirectoryPermissions() { + echo "\n=== 测试目录权限 ===\n"; + + $directories = [ + __DIR__, + __DIR__ . '/uploads/', + __DIR__ . '/css/', + __DIR__ . '/js/' + ]; + + foreach ($directories as $dir) { + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + if (is_writable($dir)) { + echo "✓ 目录可写: $dir\n"; + } else { + echo "✗ 目录不可写: $dir\n"; + } + } + + return true; +} + +// 测试AJAX端点 +function testAjaxEndpoints() { + echo "\n=== 测试AJAX端点 ===\n"; + + // 这里可以添加更详细的端点测试 + $endpoints = [ + 'check_git' => '检查Git状态', + 'get_files' => '获取文件列表', + 'git_operation' => 'Git操作', + 'upload' => '文件上传' + ]; + + foreach ($endpoints as $endpoint => $description) { + echo "✓ 端点 $endpoint ($description) 已定义\n"; + } + + return true; +} + +// 运行所有测试 +function runAllTests() { + echo "开始Git上传工具测试...\n"; + echo "=====================================\n"; + + $tests = [ + 'testPHPConfiguration', + 'testDirectoryPermissions', + 'testGitOperations', + 'testAjaxEndpoints', + 'testFileUpload' + ]; + + $allPassed = true; + + foreach ($tests as $test) { + try { + $result = $test(); + if (!$result) { + $allPassed = false; + } + } catch (Exception $e) { + echo "✗ 测试 $test 抛出异常: " . $e->getMessage() . "\n"; + $allPassed = false; + } + echo "\n"; + } + + echo "=====================================\n"; + if ($allPassed) { + echo "✓ 所有测试通过!工具应该可以正常工作。\n"; + } else { + echo "✗ 部分测试失败,请检查配置和权限。\n"; + } + + // 清理测试文件 + $testFiles = glob('test_upload_*.txt'); + foreach ($testFiles as $file) { + unlink($file); + } +} + +// 如果直接访问此文件,运行测试 +if (basename($_SERVER['PHP_SELF']) === basename(__FILE__)) { + runAllTests(); +} \ No newline at end of file diff --git a/uploads/test.php b/uploads/test.php new file mode 100644 index 0000000..3985411 --- /dev/null +++ b/uploads/test.php @@ -0,0 +1,179 @@ + ini_get('upload_max_filesize'), + 'post_max_size' => ini_get('post_max_size'), + 'max_execution_time' => ini_get('max_execution_time'), + 'memory_limit' => ini_get('memory_limit') + ]; + + foreach ($configs as $key => $value) { + echo " $key: $value\n"; + } + + // 检查必要的PHP函数 + $functions = ['exec', 'move_uploaded_file', 'json_encode', 'file_put_contents']; + $allFunctionsAvailable = true; + + foreach ($functions as $function) { + if (function_exists($function)) { + echo "✓ 函数 $function 可用\n"; + } else { + echo "✗ 函数 $function 不可用\n"; + $allFunctionsAvailable = false; + } + } + + return $allFunctionsAvailable; +} + +// 测试目录权限 +function testDirectoryPermissions() { + echo "\n=== 测试目录权限 ===\n"; + + $directories = [ + __DIR__, + __DIR__ . '/uploads/', + __DIR__ . '/css/', + __DIR__ . '/js/' + ]; + + foreach ($directories as $dir) { + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + if (is_writable($dir)) { + echo "✓ 目录可写: $dir\n"; + } else { + echo "✗ 目录不可写: $dir\n"; + } + } + + return true; +} + +// 测试AJAX端点 +function testAjaxEndpoints() { + echo "\n=== 测试AJAX端点 ===\n"; + + // 这里可以添加更详细的端点测试 + $endpoints = [ + 'check_git' => '检查Git状态', + 'get_files' => '获取文件列表', + 'git_operation' => 'Git操作', + 'upload' => '文件上传' + ]; + + foreach ($endpoints as $endpoint => $description) { + echo "✓ 端点 $endpoint ($description) 已定义\n"; + } + + return true; +} + +// 运行所有测试 +function runAllTests() { + echo "开始Git上传工具测试...\n"; + echo "=====================================\n"; + + $tests = [ + 'testPHPConfiguration', + 'testDirectoryPermissions', + 'testGitOperations', + 'testAjaxEndpoints', + 'testFileUpload' + ]; + + $allPassed = true; + + foreach ($tests as $test) { + try { + $result = $test(); + if (!$result) { + $allPassed = false; + } + } catch (Exception $e) { + echo "✗ 测试 $test 抛出异常: " . $e->getMessage() . "\n"; + $allPassed = false; + } + echo "\n"; + } + + echo "=====================================\n"; + if ($allPassed) { + echo "✓ 所有测试通过!工具应该可以正常工作。\n"; + } else { + echo "✗ 部分测试失败,请检查配置和权限。\n"; + } + + // 清理测试文件 + $testFiles = glob('test_upload_*.txt'); + foreach ($testFiles as $file) { + unlink($file); + } +} + +// 如果直接访问此文件,运行测试 +if (basename($_SERVER['PHP_SELF']) === basename(__FILE__)) { + runAllTests(); +} \ No newline at end of file