初始提交
This commit is contained in:
5
.git_remote_config.json
Normal file
5
.git_remote_config.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"remotes": {
|
||||||
|
"origin": "https:\/\/git.tsama.cn\/ll\/Demo1.git"
|
||||||
|
}
|
||||||
|
}
|
||||||
217
README.md
Normal file
217
README.md
Normal file
@@ -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
|
||||||
478
css/style.css
Normal file
478
css/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
511
index.php
Normal file
511
index.php
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Git文件上传工具
|
||||||
|
* 支持JS和CSS分离,文件上传无限制
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 设置上传限制
|
||||||
|
ini_set('upload_max_filesize', '0');
|
||||||
|
ini_set('post_max_size', '0');
|
||||||
|
ini_set('max_execution_time', '0');
|
||||||
|
ini_set('memory_limit', '-1');
|
||||||
|
|
||||||
|
// 获取当前目录
|
||||||
|
$currentDir = __DIR__;
|
||||||
|
$gitDir = $currentDir . '/.git';
|
||||||
|
$remoteConfigFile = $currentDir . '/.git_remote_config.json';
|
||||||
|
|
||||||
|
// 检查Git仓库是否存在
|
||||||
|
function checkGitRepo() {
|
||||||
|
global $gitDir;
|
||||||
|
return is_dir($gitDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化Git仓库
|
||||||
|
function initGitRepo() {
|
||||||
|
global $currentDir;
|
||||||
|
try {
|
||||||
|
exec('cd "' . $currentDir . '" && git init', $output, $returnCode);
|
||||||
|
return $returnCode === 0;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件列表
|
||||||
|
function getFileList($dir = '.') {
|
||||||
|
$files = [];
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::SELF_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Git文件上传工具</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Git文件上传工具</h1>
|
||||||
|
<div class="git-status" id="gitStatus">
|
||||||
|
<span class="status-indicator" id="gitIndicator"></span>
|
||||||
|
<span id="gitStatusText">检查Git状态...</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- 文件上传区域 -->
|
||||||
|
<section class="upload-section">
|
||||||
|
<h2>文件上传</h2>
|
||||||
|
<div class="upload-area" id="uploadArea">
|
||||||
|
<div class="upload-placeholder">
|
||||||
|
<svg class="upload-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
|
||||||
|
</svg>
|
||||||
|
<p>拖拽文件到此处或点击选择文件</p>
|
||||||
|
<p class="upload-hint">支持任意大小的文件,无数量限制</p>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="fileInput" multiple style="display: none;">
|
||||||
|
</div>
|
||||||
|
<div class="uploaded-files" id="uploadedFiles"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 文件列表 -->
|
||||||
|
<section class="files-section">
|
||||||
|
<h2>项目文件</h2>
|
||||||
|
<div class="file-controls">
|
||||||
|
<button id="refreshFiles" class="btn btn-secondary">刷新文件列表</button>
|
||||||
|
<button id="selectAllFiles" class="btn btn-secondary">全选</button>
|
||||||
|
<button id="deselectAllFiles" class="btn btn-secondary">取消全选</button>
|
||||||
|
</div>
|
||||||
|
<div class="file-list" id="fileList">
|
||||||
|
<div class="loading">加载文件中...</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Git操作区域 -->
|
||||||
|
<section class="git-section">
|
||||||
|
<h2>Git操作</h2>
|
||||||
|
|
||||||
|
<!-- 分支管理 -->
|
||||||
|
<div class="branch-config">
|
||||||
|
<h3>分支管理</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="branchSelect">当前分支:</label>
|
||||||
|
<select id="branchSelect" class="form-control">
|
||||||
|
<option value="">加载分支中...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newBranchName">新分支名称:</label>
|
||||||
|
<input type="text" id="newBranchName" placeholder="输入新分支名称">
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button id="createBranch" class="btn btn-success">创建分支</button>
|
||||||
|
<button id="switchBranch" class="btn btn-primary">切换分支</button>
|
||||||
|
<button id="deleteBranch" class="btn btn-danger">删除分支</button>
|
||||||
|
<button id="refreshBranches" class="btn btn-secondary">刷新分支</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr style="margin: 20px 0;">
|
||||||
|
|
||||||
|
<!-- 远程仓库配置 -->
|
||||||
|
<div class="remote-config">
|
||||||
|
<h3>远程仓库配置</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="remoteName">远程仓库名称:</label>
|
||||||
|
<input type="text" id="remoteName" placeholder="例如: origin" value="origin">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="remoteUrl">仓库地址:</label>
|
||||||
|
<input type="text" id="remoteUrl" placeholder="例如: https://github.com/username/repo.git">
|
||||||
|
</div>
|
||||||
|
<button id="addRemote" class="btn btn-info">添加远程仓库</button>
|
||||||
|
<button id="showRemotes" class="btn btn-secondary">查看远程仓库</button>
|
||||||
|
|
||||||
|
<div class="remote-list" id="remoteList"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr style="margin: 20px 0;">
|
||||||
|
|
||||||
|
<div class="git-controls">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="commitMessage">提交信息:</label>
|
||||||
|
<input type="text" id="commitMessage" placeholder="输入提交信息..." value="自动提交">
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button id="gitAdd" class="btn btn-primary">添加到暂存区</button>
|
||||||
|
<button id="gitCommit" class="btn btn-success">提交更改</button>
|
||||||
|
<button id="gitPush" class="btn btn-warning">推送到远程</button>
|
||||||
|
<button id="gitStatusBtn" class="btn btn-info">查看状态</button>
|
||||||
|
<button id="gitLog" class="btn btn-secondary">查看日志</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="git-output" id="gitOutput"></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
593
js/main.js
Normal file
593
js/main.js
Normal file
@@ -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 = `
|
||||||
|
<span>${file}</span>
|
||||||
|
<span style="color: #27ae60;">✓</span>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div>
|
||||||
|
<div class="remote-name">${name} (${info.type})</div>
|
||||||
|
<div class="remote-url">${info.url}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
remoteList.appendChild(remoteItem);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
remoteList.innerHTML = '<div class="loading">暂无远程仓库</div>';
|
||||||
|
}
|
||||||
|
} 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 = '<option value="">暂无分支</option>';
|
||||||
|
}
|
||||||
|
} 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 = '<div class="loading">暂无文件</div>';
|
||||||
|
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();
|
||||||
|
});
|
||||||
179
test.php
Normal file
179
test.php
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Git上传工具测试文件
|
||||||
|
* 用于验证各个功能模块是否正常工作
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 测试文件上传功能
|
||||||
|
function testFileUpload() {
|
||||||
|
echo "=== 测试文件上传功能 ===\n";
|
||||||
|
|
||||||
|
// 创建测试文件
|
||||||
|
$testContent = "这是一个测试文件\n创建于: " . date('Y-m-d H:i:s');
|
||||||
|
$testFile = 'test_upload_' . time() . '.txt';
|
||||||
|
|
||||||
|
if (file_put_contents($testFile, $testContent)) {
|
||||||
|
echo "✓ 测试文件创建成功: $testFile\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ 测试文件创建失败\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查上传目录
|
||||||
|
$uploadDir = __DIR__ . '/uploads/';
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0777, true);
|
||||||
|
echo "✓ 上传目录创建成功\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试Git操作功能
|
||||||
|
function testGitOperations() {
|
||||||
|
echo "\n=== 测试Git操作功能 ===\n";
|
||||||
|
|
||||||
|
$currentDir = __DIR__;
|
||||||
|
|
||||||
|
// 检查Git是否可用
|
||||||
|
exec('git --version', $output, $returnCode);
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
echo "✓ Git已安装: " . $output[0] . "\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ Git未安装或不可用\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Git仓库
|
||||||
|
$gitDir = $currentDir . '/.git';
|
||||||
|
if (is_dir($gitDir)) {
|
||||||
|
echo "✓ Git仓库已存在\n";
|
||||||
|
} else {
|
||||||
|
echo "ℹ Git仓库不存在,可以初始化\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试PHP配置
|
||||||
|
function testPHPConfiguration() {
|
||||||
|
echo "\n=== 测试PHP配置 ===\n";
|
||||||
|
|
||||||
|
$configs = [
|
||||||
|
'upload_max_filesize' => 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();
|
||||||
|
}
|
||||||
179
uploads/test.php
Normal file
179
uploads/test.php
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Git上传工具测试文件
|
||||||
|
* 用于验证各个功能模块是否正常工作
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 测试文件上传功能
|
||||||
|
function testFileUpload() {
|
||||||
|
echo "=== 测试文件上传功能 ===\n";
|
||||||
|
|
||||||
|
// 创建测试文件
|
||||||
|
$testContent = "这是一个测试文件\n创建于: " . date('Y-m-d H:i:s');
|
||||||
|
$testFile = 'test_upload_' . time() . '.txt';
|
||||||
|
|
||||||
|
if (file_put_contents($testFile, $testContent)) {
|
||||||
|
echo "✓ 测试文件创建成功: $testFile\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ 测试文件创建失败\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查上传目录
|
||||||
|
$uploadDir = __DIR__ . '/uploads/';
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0777, true);
|
||||||
|
echo "✓ 上传目录创建成功\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试Git操作功能
|
||||||
|
function testGitOperations() {
|
||||||
|
echo "\n=== 测试Git操作功能 ===\n";
|
||||||
|
|
||||||
|
$currentDir = __DIR__;
|
||||||
|
|
||||||
|
// 检查Git是否可用
|
||||||
|
exec('git --version', $output, $returnCode);
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
echo "✓ Git已安装: " . $output[0] . "\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ Git未安装或不可用\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Git仓库
|
||||||
|
$gitDir = $currentDir . '/.git';
|
||||||
|
if (is_dir($gitDir)) {
|
||||||
|
echo "✓ Git仓库已存在\n";
|
||||||
|
} else {
|
||||||
|
echo "ℹ Git仓库不存在,可以初始化\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试PHP配置
|
||||||
|
function testPHPConfiguration() {
|
||||||
|
echo "\n=== 测试PHP配置 ===\n";
|
||||||
|
|
||||||
|
$configs = [
|
||||||
|
'upload_max_filesize' => 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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user