10 Commits

Author SHA1 Message Date
zyj
c787c99622 feat: v0.4.0 — 配置架构重构 + 一键多端推送 + 项目级代理控制\n\n架构重构:\n- 配置从 Platform→User→Project 重构为扁平的 Projects+Groups+Credentials 模型\n- 侧边栏完全重写,适配新分组→项目树结构\n- 新增分组(Group)管理和凭据(Credential)管理\n\n新功能:\n- 一键多端推送:Push All 按钮,将当前分支/标签推送到所有远程仓库\n- 项目级代理控制:每个项目独立设置代理(跟随全局/强制开启/强制关闭)\n- GitClient 新增 RunWithProxy 方法,支持按调用级别覆盖全局代理\n- 所有网络操作(Pull/Push/Fetch)均支持项目级代理设置\n\n其他:\n- 新增 CHANGELOG.md 版本更新日志\n- 更新 README/README_CN 文档,反映最新 72+ API 方法\n- 版本号升级到 0.4.0(config.yml/info.json/Info.plist)
Some checks failed
Release / build-windows (push) Has been cancelled
2026-03-12 11:41:26 +08:00
zyj
3ec453cc38 feat: 多 remote 远程仓库支持 (v0.3.1)\n\n后端:\n- 新增 RemoteList/AddRemote/RemoveRemote 远程仓库管理\n- 所有远程操作(Push/Pull/Fetch/PushTag/DeleteTag/DeleteRemoteBranch等)支持指定 remote\n- 新增 PushTo/PullFrom/FetchRemote/PushTagTo/DeleteRemoteTagFrom 等方法\n- BatchPull/BatchPush 支持 remote 参数\n\n前端:\n- 顶部信息栏新增 remote 选择器下拉菜单\n- 支持切换当前操作的远程仓库\n- 支持添加/删除远程仓库\n- Pull/Push/Fetch/标签推送/远程分支删除等操作均使用选中的 remote
Some checks failed
Release / build-windows (push) Has been cancelled
2026-03-12 10:57:27 +08:00
zyj
3d6874f79e chore: 升级版本号到 v0.3.0\n\n- Windows info.json: 0.2.0 → 0.3.0\n- macOS Info.plist: 0.2.0 → 0.3.0\n- build/config.yml: 0.1.0 → 0.3.0(修复上次遗漏) 2026-03-11 10:31:20 +08:00
zyj
d6dbd8834b fix: 修复 GitCheck 组件 @wailsio/runtime Events 导入错误 2026-03-11 10:28:58 +08:00
zyj
beb40688ce feat: 启动时检测 Git 安装状态,支持自动下载安装\n\n- 启动时自动检测 Git 是否已安装\n- 未安装时显示全屏提示界面\n- 支持用户选择安装路径\n- 自动从 GitHub 下载 Git for Windows 并静默安装\n- 实时显示下载进度和安装状态\n- 安装完成后自动重新初始化 Git 客户端" 2026-03-11 10:18:26 +08:00
zyj
5154bb5564 chore: 升级版本号到 v0.2.0,修复 macOS/Linux 配置\n\n- 升级所有平台版本号 0.1.0 → 0.2.0\n- 修复 macOS Info.plist 残留的旧项目名 Peter Agent → GitPilot\n- 修复 Linux desktop 文件残留的旧项目名" 2026-03-11 10:10:37 +08:00
zyj
af15e86df6 feat: 新增冲突处理、提交搜索、批量操作功能\n\n冲突处理:\n- 检测合并状态并显示冲突提示栏\n- 冲突文件列表展示和内容编辑器\n- 支持保存冲突文件、标记已解决、中止合并、完成合并\n\n提交搜索:\n- 支持按提交信息关键字和作者搜索\n- 搜索结果与完整历史切换显示\n\n批量操作:\n- 侧边栏新增批量操作入口\n- 展示所有项目概览(分支/变更/未推送状态)\n- 支持批量 Pull/Push 并显示执行结果" 2026-03-11 10:06:28 +08:00
zyj
6354cbfc94 feat: 新增分支管理、Stash贮藏、远程分支、设置页面\n\n分支管理:\n- 创建新分支\n- 删除本地分支(安全/强制)\n- 合并分支到当前分支\n\n远程分支:\n- 查看远程分支列表\n- 检出远程分支到本地\n- 删除远程分支\n\nStash 贮藏:\n- 保存当前变更(支持自定义消息)\n- 应用/弹出/删除贮藏\n- 贮藏列表管理\n\n设置:\n- Git 全局配置 (user.name / user.email)\n- 设置弹窗 UI" 2026-03-11 09:37:58 +08:00
zyj
cb6b21ef98 refactor: 拆分 proxy.go 为平台特定文件,兼容 macOS 和 Linux\n\n- proxy.go: 保留通用类型和辅助函数\n- proxy_windows.go: 通过注册表读取 Windows 系统代理\n- proxy_darwin.go: 通过 scutil --proxy 读取 macOS 系统代理\n- proxy_linux.go: 通过 gsettings/kreadconfig 读取 Linux 系统代理" 2026-03-10 11:55:34 +08:00
zyj
6e0becb748 新增远程项目克隆功能 2026-03-10 10:25:45 +08:00
28 changed files with 5394 additions and 777 deletions

70
CHANGELOG.md Normal file
View File

@@ -0,0 +1,70 @@
# Changelog
## v0.4.0 (2026-03-12)
### 🔧 架构重构
- **配置架构重构**:从 `Platform → User → Project` 层级结构重构为扁平的 `Projects + Groups + Credentials` 模型
- 项目支持多远程仓库指向不同平台,不再受限于单一平台层级
- 新增分组Group管理支持自定义分组和图标
- 新增凭据Credential管理支持 GitHub / Gitee / Gitea 等多平台凭据
- 侧边栏完全重写,适配新的分组→项目树结构
### ✨ 新功能
- **一键多端推送**Push All 按钮,一键将当前分支推送到所有远程仓库
- 顶部操作栏新增 "Push All" 按钮(仅在有多个远程仓库时启用)
- 标签管理新增 "推送到所有远程" 按钮
- 推送结果逐一展示每个远程仓库的成功/失败状态
- **项目代理开关**:每个项目可独立控制是否使用系统代理
- 三种模式:跟随全局 / 强制使用代理 / 强制不使用代理
- 顶部操作栏新增代理设置下拉菜单
- Pull / Push / Fetch / Push All 等所有网络操作均尊重项目级代理设置
### 🔨 后端改进
- `GitClient` 新增 `RunWithProxy()` 方法,支持按调用级别覆盖全局代理设置
- 所有网络操作Pull/Push/Fetch/PushToAllRemotes/PushTagToAllRemotes均支持项目级代理
- 配置管理方法重构Group CRUD / Credential CRUD / Project 分组管理
- 新增 `PushToAllRemotes()` / `PushTagToAllRemotes()` 服务方法
- 新增 `GetProjectProxy()` / `SetProjectProxy()` 服务方法
---
## v0.3.1 (2026-02-27)
### ✨ 新功能
- **多 Remote 远程仓库支持**
- 新增 RemoteList / AddRemote / RemoveRemote 远程仓库管理
- 所有远程操作支持指定 remotePush/Pull/Fetch/PushTag/DeleteTag 等)
- 顶部信息栏新增 remote 选择器下拉菜单
- 支持切换当前操作的远程仓库、添加/删除远程仓库
---
## v0.3.0 (2026-02-25)
### ✨ 新功能
- **Git 安装检测**:启动时检测 Git 是否已安装,支持自动下载安装 Git for Windows
- **冲突处理**:合并冲突检测、冲突文件编辑器、标记已解决、中止/完成合并
- **提交搜索**:支持按提交信息关键字和作者搜索
- **批量操作**:侧边栏批量 Pull/Push 所有项目
---
## v0.2.0 (2026-02-20)
### ✨ 新功能
- 分支管理(创建/删除/合并)
- 远程分支(查看/检出/删除)
- Stash 贮藏管理
- Git 全局配置设置user.name / user.email
- Tag 标签管理
- CI/CD 发布流水线
- NSIS 安装包
---
## v0.1.0
- 初始版本
- 基本 Git 操作Clone / Pull / Push / Commit
- 文件变更查看和 Diff 展示
- 系统代理自动检测

121
README.md
View File

@@ -4,7 +4,7 @@
A cross-platform desktop Git repository management tool built with **Go + Wails v3 + Nuxt 4 + Vue 3**.
Manage multiple Git repositories across different platforms (GitHub, Gitee, Gitea, etc.) from a single unified interface — like a lightweight, standalone version of VS Code's Git panel.
Manage multiple Git repositories across different platforms (GitHub, Gitee, Gitea, etc.) from a single unified interface — like a lightweight, standalone version of VS Codes Git panel.
![Go](https://img.shields.io/badge/Go-1.25-00ADD8?logo=go&logoColor=white)
![Wails](https://img.shields.io/badge/Wails-v3-red?logo=go&logoColor=white)
@@ -14,10 +14,12 @@ Manage multiple Git repositories across different platforms (GitHub, Gitee, Gite
## ✨ Features
### Multi-Platform Management
### Project & Group Management
- Manage repositories from **GitHub**, **Gitee**, **Gitea**, and custom Git platforms
- Organize projects by **Platform → User → Project** hierarchy
- Add / remove platforms, users, and projects through the GUI
- Organize projects into custom **Groups** (e.g. `github/user`, `gitea`, `work`)
- Manage **Credentials** per platform (GitHub / Gitee / Gitea with custom base URL)
- **Clone** remote repositories directly from the GUI with group assignment
- Add / remove / move projects between groups
### VS Code-Style Git Operations
- **View changed files** with a collapsible directory tree structure
@@ -27,51 +29,96 @@ Manage multiple Git repositories across different platforms (GitHub, Gitee, Gite
- **Diff viewer** with syntax-highlighted additions and deletions
- **Binary file detection** — skips preview for images, executables, etc.
### Multi-Remote Support
- Manage multiple remotes per project (origin, upstream, etc.)
- **Remote selector** dropdown to switch active remote
- Add / remove remotes through the GUI
- **Push to All Remotes** — one-click push current branch to every remote
- **Push Tag to All Remotes** — push tags to all remotes at once
### Per-Project Proxy Control
- Each project can independently control proxy usage
- Three modes: **Follow Global** / **Force Proxy On** / **Force Proxy Off**
- All network operations (Pull / Push / Fetch / Push All) respect per-project proxy settings
- System proxy auto-detection from Windows registry / macOS scutil / Linux gsettings
### Commit History
- Browse commit history with author, time, and message
- Click a commit to view its **changed file list** (like VS Code)
- Click a commit to view its **changed file list**
- Select a file to view its **per-file diff**
- **Version rollback** — hard / mixed / soft reset with confirmation dialog
- **Revert commit** — create a reverse commit
- **Search commits** — filter by message keyword and/or author
### Branch Management
- View all local branches with current branch indicator
- View all local and remote branches
- **Create / Delete / Merge** branches
- **Switch branches** via dropdown selector
- **Pull / Push / Fetch** with current branch awareness
- **Checkout remote branches** to local
- **Delete remote branches**
### Tag Management
- View all tags with hash, message, and timestamp
- **Create tags** (lightweight or annotated)
- **Push tags** to selected remote or all remotes
- **Delete tags** (local and remote)
### Stash Management
- **Save** current changes with optional message
- **Apply / Pop / Drop** stash entries
- View stash list with descriptions
### Merge Conflict Resolution
- Detect merge state and display conflict indicator
- View and edit conflict files in a built-in editor
- **Mark as resolved / Abort merge / Complete merge**
### Batch Operations
- View all projects overview (branch, changes, push status)
- **Batch Pull / Push** all projects at once
- Results displayed per project
### Git Installation Detection
- Auto-detect Git installation on startup
- If not installed, show a full-screen guide
- **Auto-download and install** Git for Windows with progress tracking
### Desktop Experience
- **System tray** — close window hides to tray, click tray icon to restore
- **Resizable panels** — drag splitters between sidebar, file list, and viewer
- **Dark theme** — Catppuccin Mocha color scheme with Ant Design Vue
- **Native folder picker** — select project paths via OS file dialog
- Git global config editor (user.name / user.email)
## 🏗️ Tech Stack
| Layer | Technology |
|-------|-----------|
| Backend | Go 1.25, Wails v3 (alpha.74) |
| Backend | Go 1.25, Wails v3 |
| Frontend | Nuxt 4, Vue 3.5, Vite 7 |
| UI Library | Ant Design Vue 4, @ant-design/icons-vue |
| Git Operations | Git CLI with proxy support, 30s timeout |
| Git Operations | Git CLI wrapper with per-project proxy support |
| Config | YAML-based persistent configuration |
| Build | Taskfile v3 |
| Build | Taskfile v3, NSIS installer (Windows) |
## 📁 Project Structure
```
GitPilot/
├── main.go # Wails app entry, system tray, window management
├── config.yaml # Platform/user/project configuration
├── cmd/main.go # Wails app entry, system tray, window management
├── config.yaml # Projects / Groups / Credentials configuration
├── CHANGELOG.md # Version history
├── Taskfile.yml # Build tasks
├── go.mod
├── cmd/ # CLI utilities
├── config/
│ └── config.go # YAML config loader/saver
│ └── config.go # YAML config types & loader/saver
├── internal/
│ ├── proxy.go # System proxy detection
│ ├── proxy.go # System proxy detection (Windows/macOS/Linux)
│ ├── git/
│ │ └── client.go # Git CLI wrapper (34 operations)
│ │ └── client.go # Git CLI wrapper with RunWithProxy support
│ └── app/
── service.go # AppService — 34 methods exposed to frontend
── service.go # AppService — 72 methods exposed to frontend
│ └── gitsetup.go # Git installation detection & auto-install
├── frontend/
│ ├── nuxt.config.ts
│ ├── package.json
@@ -81,10 +128,12 @@ GitPilot/
│ ├── layouts/
│ │ └── default.vue # Ant Design dark theme provider
│ └── components/
│ ├── Sidebar.vue # Platform/user/project tree sidebar
│ ├── ContentArea.vue # Git status, diff, history, branches
── FileTreeNode.vue # Recursive file tree with actions
└── build/ # Wails build configuration
│ ├── Sidebar.vue # Group/project tree, batch ops, credentials
│ ├── ContentArea.vue # Git status, diff, history, branches, tags
── FileTreeNode.vue # Recursive file tree with stage/discard
│ ├── CommitFileTreeNode.vue # Commit file tree viewer
│ └── GitCheck.vue # Git installation check screen
└── build/ # Wails build config, NSIS installer scripts
```
## 🚀 Getting Started
@@ -108,14 +157,9 @@ cd GitPilot
cd frontend && npm install && cd ..
# Generate Wails bindings
wails3 generate bindings
# Build frontend
cd frontend && npx nuxi generate && cd ..
wails3 generate bindings -d frontend/bindings
# Run in development mode
task dev
# or
wails3 dev -config ./build/config.yml -port 9245
```
@@ -129,19 +173,28 @@ The binary will be output to the `bin/` directory.
## 📋 API Overview
GitPilot exposes **34 backend methods** to the frontend via Wails bindings:
GitPilot exposes **72+ backend methods** to the frontend via Wails bindings:
| Category | Methods |
|----------|---------|
| **Projects** | GetProjectTree, AddProject, RemoveProject, SelectDirectory |
| **Platforms** | AddPlatform, UpdatePlatform, RemovePlatform, GetPlatformInfo |
| **Users** | AddUser, UpdateUser, RemoveUser, GetUserInfo |
| **Project Management** | GetProjectTree, AddProject, RemoveProject, CloneProject, MoveProjectToGroup, SelectDirectory |
| **Group Management** | GetGroups, AddGroup, UpdateGroup, RemoveGroup |
| **Credential Management** | GetCredentials, AddCredential, UpdateCredential, RemoveCredential |
| **Git Status** | GetProjectStatus, GetProjectChangedFiles, GetFileContent, GetFileDiff, GetFileDiffStaged |
| **Staging** | StageFiles, UnstageFiles, StageAll, UnstageAll |
| **Commit** | CommitChanges, DiscardFiles |
| **Remote** | PullProject, PushProject, FetchProject |
| **History** | GetCommitLog, GetCommitDiff, GetCommitFiles, GetCommitFileDiff |
| **Branches** | GetBranches, SwitchBranch, ResetProject |
| **Remote Operations** | PullProject, PushProject, FetchProject, PushToAllRemotes, PushTagToAllRemotes |
| **Remote Management** | GetRemotes, AddRemote, RemoveRemote |
| **Proxy Control** | GetProjectProxy, SetProjectProxy |
| **Commit History** | GetCommitLog, GetCommitDiff, GetCommitFiles, GetCommitFileDiff, RevertCommit, SearchCommitLog |
| **Branch Management** | GetBranches, SwitchBranch, CreateBranch, DeleteBranch, MergeBranch, ResetProject |
| **Remote Branches** | GetRemoteBranches, CheckoutRemoteBranch, DeleteRemoteBranch |
| **Tags** | GetTags, CreateTag, DeleteTag, PushTag |
| **Stash** | StashSave, GetStashList, StashApply, StashPop, StashDrop |
| **Conflict Resolution** | IsMerging, GetConflictFiles, GetConflictFileContent, SaveConflictFile, ResolveConflictFile, AbortMerge |
| **Batch Operations** | GetAllProjectOverview, BatchPull, BatchPush |
| **Settings** | GetGitGlobalConfig, SetGitGlobalConfig, GetAppSettings, UpdateAppSettings |
| **Git Setup** | CheckGitInstalled, SelectGitInstallDir, InstallGit |
## 📄 License

View File

@@ -14,10 +14,12 @@
## ✨ 功能特性
### 多平台管理
### 项目与分组管理
- 管理来自 **GitHub**、**Gitee**、**Gitea** 及自建 Git 平台的仓库
- **平台 → 用户 → 项目** 层级组织
- 通过图形界面添加/删除平台、用户和项目
-自定义**分组**组织项目(如 `github/user``gitea``work`
- 按平台管理**凭据**GitHub / Gitee / Gitea支持自定义 Base URL
- 直接在界面中**克隆**远程仓库并分配分组
- 添加 / 删除 / 移动项目到不同分组
### VS Code 风格的 Git 操作
- 以可折叠的**目录树结构**查看变更文件
@@ -27,51 +29,96 @@
- **Diff 查看器** — 语法高亮显示增删内容
- **二进制文件检测** — 自动跳过图片、可执行文件等的预览
### 多远程仓库支持
- 每个项目支持管理多个远程仓库origin、upstream 等)
- **远程仓库选择器** — 下拉切换当前操作的远程仓库
- 通过界面添加 / 删除远程仓库
- **一键推送到所有远程** — 将当前分支推送到全部远程仓库
- **标签多端推送** — 一键将标签推送到所有远程仓库
### 项目级代理控制
- 每个项目可独立控制是否使用代理
- 三种模式:**跟随全局** / **强制使用代理** / **强制不使用代理**
- 所有网络操作Pull / Push / Fetch / Push All均尊重项目级代理设置
- 系统代理自动检测Windows 注册表 / macOS scutil / Linux gsettings
### 提交历史
- 浏览提交历史,显示作者、时间和提交信息
- 点击提交查看该版本的**变更文件列表**(与 VS Code 一致)
- 点击提交查看该版本的**变更文件列表**
- 选中文件查看**单文件 diff**
- **版本回滚** — 支持硬回滚/混合回滚/软回滚,操作前弹出确认对话框
- **撤销提交** — 生成反向提交
- **提交搜索** — 按提交信息关键字和/或作者过滤
### 分支管理
- 查看所有本地分支,标记当前分支
- 查看所有本地分支和远程分支
- **创建 / 删除 / 合并**分支
- 通过下拉菜单**切换分支**
- **拉取/推送/获取远程信息**,自动识别当前分支
- **检出远程分支**到本地
- **删除远程分支**
### 标签管理
- 查看所有标签(显示哈希、描述、时间戳)
- **创建标签**(轻量标签或附注标签)
- **推送标签**到指定远程或所有远程
- **删除标签**(本地和远程同步删除)
### 贮藏管理
- **保存**当前变更(支持自定义描述)
- **应用 / 弹出 / 删除**贮藏条目
- 查看贮藏列表及描述
### 合并冲突处理
- 自动检测合并状态并显示冲突提示
- 在内置编辑器中查看和编辑冲突文件
- **标记已解决 / 中止合并 / 完成合并**
### 批量操作
- 查看所有项目概览(分支、变更数、推送状态)
- **批量 Pull / Push** 所有项目
- 按项目显示操作结果
### Git 安装检测
- 启动时自动检测 Git 是否已安装
- 未安装时显示全屏引导界面
- 支持**自动下载安装** Git for Windows实时显示进度
### 桌面体验
- **系统托盘** — 关闭窗口自动隐藏到托盘,点击托盘图标恢复窗口
- **系统托盘** — 关闭窗口自动隐藏到托盘,点击图标恢复
- **可拖拽调整面板** — 侧边栏、文件列表、查看器之间的分隔条可拖拽
- **暗色主题** — Catppuccin Mocha 配色方案 + Ant Design Vue
- **原生文件夹选择器** — 通过系统对话框选择项目路径
- Git 全局配置编辑user.name / user.email
## 🏗️ 技术栈
| 层级 | 技术 |
|------|------|
| 后端 | Go 1.25, Wails v3 (alpha.74) |
| 后端 | Go 1.25, Wails v3 |
| 前端 | Nuxt 4, Vue 3.5, Vite 7 |
| UI 组件库 | Ant Design Vue 4, @ant-design/icons-vue |
| Git 操作 | Git CLI 封装,支持代理30 秒超时 |
| Git 操作 | Git CLI 封装,支持项目级代理控制 |
| 配置 | 基于 YAML 的持久化配置 |
| 构建 | Taskfile v3 |
| 构建 | Taskfile v3, NSIS 安装包Windows |
## 📁 项目结构
```
GitPilot/
├── main.go # Wails 应用入口,系统托盘,窗口管理
├── config.yaml # 平台/用户/项目配置文件
├── cmd/main.go # Wails 应用入口,系统托盘,窗口管理
├── config.yaml # 项目 / 分组 / 凭据配置文件
├── CHANGELOG.md # 版本更新日志
├── Taskfile.yml # 构建任务
├── go.mod
├── cmd/ # 命令行工具
├── config/
│ └── config.go # YAML 配置加载/保存
│ └── config.go # YAML 配置类型 & 加载/保存
├── internal/
│ ├── proxy.go # 系统代理检测
│ ├── proxy.go # 系统代理检测Windows/macOS/Linux
│ ├── git/
│ │ └── client.go # Git CLI 封装34 个操作)
│ │ └── client.go # Git CLI 封装,支持 RunWithProxy
│ └── app/
── service.go # AppService — 34 个方法暴露给前端
── service.go # AppService — 72 个方法暴露给前端
│ └── gitsetup.go # Git 安装检测 & 自动安装
├── frontend/
│ ├── nuxt.config.ts
│ ├── package.json
@@ -81,10 +128,12 @@ GitPilot/
│ ├── layouts/
│ │ └── default.vue # Ant Design 暗色主题配置
│ └── components/
│ ├── Sidebar.vue # 平台/用户/项目树形侧边栏
│ ├── ContentArea.vue # Git 状态、Diff、历史、分支
── FileTreeNode.vue # 递归文件树组件(含操作按钮
└── build/ # Wails 构建配置
│ ├── Sidebar.vue # 分组/项目树、批量操作、凭据管理
│ ├── ContentArea.vue # Git 状态、Diff、历史、分支、标签
── FileTreeNode.vue # 递归文件树(含暂存/丢弃操作
├── CommitFileTreeNode.vue # 提交文件树查看器
│ └── GitCheck.vue # Git 安装检测界面
└── build/ # Wails 构建配置NSIS 安装脚本
```
## 🚀 快速开始
@@ -108,14 +157,9 @@ cd GitPilot
cd frontend && npm install && cd ..
# 生成 Wails 绑定
wails3 generate bindings
# 构建前端
cd frontend && npx nuxi generate && cd ..
wails3 generate bindings -d frontend/bindings
# 开发模式运行
task dev
# 或者
wails3 dev -config ./build/config.yml -port 9245
```
@@ -129,19 +173,28 @@ task build
## 📋 API 概览
GitPilot 通过 Wails 绑定向前端暴露了 **34 个后端方法**
GitPilot 通过 Wails 绑定向前端暴露了 **72+ 个后端方法**
| 类别 | 方法 |
|------|------|
| **项目管理** | GetProjectTree, AddProject, RemoveProject, SelectDirectory |
| **平台管理** | AddPlatform, UpdatePlatform, RemovePlatform, GetPlatformInfo |
| **用户管理** | AddUser, UpdateUser, RemoveUser, GetUserInfo |
| **项目管理** | GetProjectTree, AddProject, RemoveProject, CloneProject, MoveProjectToGroup, SelectDirectory |
| **分组管理** | GetGroups, AddGroup, UpdateGroup, RemoveGroup |
| **凭据管理** | GetCredentials, AddCredential, UpdateCredential, RemoveCredential |
| **Git 状态** | GetProjectStatus, GetProjectChangedFiles, GetFileContent, GetFileDiff, GetFileDiffStaged |
| **暂存操作** | StageFiles, UnstageFiles, StageAll, UnstageAll |
| **提交操作** | CommitChanges, DiscardFiles |
| **远程操作** | PullProject, PushProject, FetchProject |
| **提交历史** | GetCommitLog, GetCommitDiff, GetCommitFiles, GetCommitFileDiff |
| **分支管理** | GetBranches, SwitchBranch, ResetProject |
| **远程操作** | PullProject, PushProject, FetchProject, PushToAllRemotes, PushTagToAllRemotes |
| **远程仓库管理** | GetRemotes, AddRemote, RemoveRemote |
| **代理控制** | GetProjectProxy, SetProjectProxy |
| **提交历史** | GetCommitLog, GetCommitDiff, GetCommitFiles, GetCommitFileDiff, RevertCommit, SearchCommitLog |
| **分支管理** | GetBranches, SwitchBranch, CreateBranch, DeleteBranch, MergeBranch, ResetProject |
| **远程分支** | GetRemoteBranches, CheckoutRemoteBranch, DeleteRemoteBranch |
| **标签管理** | GetTags, CreateTag, DeleteTag, PushTag |
| **贮藏管理** | StashSave, GetStashList, StashApply, StashPop, StashDrop |
| **冲突处理** | IsMerging, GetConflictFiles, GetConflictFileContent, SaveConflictFile, ResolveConflictFile, AbortMerge |
| **批量操作** | GetAllProjectOverview, BatchPull, BatchPush |
| **设置** | GetGitGlobalConfig, SetGitGlobalConfig, GetAppSettings, UpdateAppSettings |
| **Git 安装** | CheckGitInstalled, SelectGitInstallDir, InstallGit |
## 📄 开源协议

View File

@@ -9,7 +9,7 @@ info:
description: "Git Repository Management Tool"
copyright: "(c) 2025, GitPilot"
comments: "GitPilot - Git Repository Manager"
version: "0.1.0"
version: "0.4.0"
dev_mode:
root_path: .

View File

@@ -4,17 +4,17 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>Peter Agent</string>
<string>GitPilot</string>
<key>CFBundleExecutable</key>
<string>peter-agent</string>
<string>gitpilot</string>
<key>CFBundleIdentifier</key>
<string>com.peteragent.app</string>
<string>com.gitpilot.app</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<string>0.2.0</string>
<key>CFBundleGetInfoString</key>
<string>AI Agent Application</string>
<string>Git Repository Management Tool</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.2.0</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
@@ -22,7 +22,7 @@
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>© 2025, Peter Agent</string>
<string>© 2025, GitPilot</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>

View File

@@ -4,17 +4,17 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>Peter Agent</string>
<string>GitPilot</string>
<key>CFBundleExecutable</key>
<string>peter-agent</string>
<string>gitpilot</string>
<key>CFBundleIdentifier</key>
<string>com.peteragent.app</string>
<string>com.gitpilot.app</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<string>0.4.0</string>
<key>CFBundleGetInfoString</key>
<string>AI Agent Application</string>
<string>Git Repository Management Tool</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.4.0</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
@@ -22,6 +22,6 @@
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>© 2025, Peter Agent</string>
<string>© 2025, GitPilot</string>
</dict>
</plist>

View File

@@ -1,13 +1,13 @@
[Desktop Entry]
Version=1.0
Name=Peter Agent
Comment=AI Agent Application
Name=GitPilot
Comment=Git Repository Management Tool
# The Exec line includes %u to pass the URL to the application
Exec=/usr/local/bin/peter-agent %u
Exec=/usr/local/bin/gitpilot %u
Terminal=false
Type=Application
Icon=peter-agent
Icon=gitpilot
Categories=Utility;
StartupWMClass=peter-agent
StartupWMClass=gitpilot

View File

@@ -1,10 +1,10 @@
{
"fixed": {
"file_version": "0.1.0"
"file_version": "0.4.0"
},
"info": {
"0000": {
"ProductVersion": "0.1.0",
"ProductVersion": "0.4.0",
"CompanyName": "GitPilot",
"FileDescription": "Git Repository Management Tool",
"LegalCopyright": "© 2025, GitPilot",

View File

@@ -1,22 +1,22 @@
platforms:
gitea:
projects:
- name: GitPilot
path: F:\Project\GitPilot
group: github/zhuy1228
groups:
- name: github/zhuy1228
icon: github
- name: gitee/zhuyuj
icon: gitee
- name: gitea
icon: gitea
credentials:
- platform: github
username: zhuy1228
- platform: gitee
username: zhuyuj
- platform: gitea
base_url: http://192.168.1.10:3000
users: []
gitee:
base_url: ""
users:
- username: zhuyuj
token: ""
projects: []
github:
base_url: ""
users:
- username: zhuy1228
token: ""
projects:
- name: GitPilot
path: F:\Project\GitPilot
enabled: false
username: ""
settings:
concurrency: 6
network_check: true

View File

@@ -8,32 +8,41 @@ import (
"gopkg.in/yaml.v3"
)
type Project struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Enabled bool `yaml:"enabled"` // 可选,默认 true
// ProjectItem 项目配置(以路径为核心)
type ProjectItem struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Group string `yaml:"group,omitempty"` // 所属分组名,可为空表示"未分组"
UseProxy *bool `yaml:"use_proxy,omitempty"` // 是否走系统代理nil 表示跟随全局
}
type User struct {
Username string `yaml:"username"`
Token string `yaml:"token"` // 可选:未来扩展 API 功能
Projects []Project `yaml:"projects"`
// Group 项目分组
type Group struct {
Name string `yaml:"name"`
Icon string `yaml:"icon,omitempty"` // 可选图标标识
}
type Platform struct {
BaseURL string `yaml:"base_url"` // GitHub/Gitee 可为空Gitea 需要
Users []User `yaml:"users"`
// Credential 凭证信息(独立于项目)
type Credential struct {
Platform string `yaml:"platform"` // github / gitee / gitea / ...
BaseURL string `yaml:"base_url,omitempty"` // 自建平台地址
Username string `yaml:"username"`
Token string `yaml:"token,omitempty"`
}
// Settings 应用设置
type Settings struct {
Concurrency int `yaml:"concurrency"`
NetworkCheck bool `yaml:"network_check"`
LogLevel string `yaml:"log_level"`
}
// AppConfig 应用配置(扁平化结构)
type AppConfig struct {
Platforms map[string]Platform `yaml:"platforms"`
Settings Settings `yaml:"settings"`
Projects []ProjectItem `yaml:"projects"`
Groups []Group `yaml:"groups"`
Credentials []Credential `yaml:"credentials"`
Settings Settings `yaml:"settings"`
}
// configPath 返回 config.yaml 的绝对路径(与可执行文件同目录)
@@ -53,18 +62,26 @@ func LoadConfig() (*AppConfig, error) {
}
defer file.Close()
var appConfig *AppConfig
err = yaml.NewDecoder(file).Decode(&appConfig)
if err != nil {
var appConfig AppConfig
if err := yaml.NewDecoder(file).Decode(&appConfig); err != nil {
return nil, err
}
return appConfig, nil
if appConfig.Projects == nil {
appConfig.Projects = []ProjectItem{}
}
if appConfig.Groups == nil {
appConfig.Groups = []Group{}
}
if appConfig.Credentials == nil {
appConfig.Credentials = []Credential{}
}
return &appConfig, nil
}
// SaveConfig 将配置保存到 config.yaml 文件
func SaveConfig(config *AppConfig) error {
func SaveConfig(cfg *AppConfig) error {
file, err := os.OpenFile(configPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
@@ -72,10 +89,8 @@ func SaveConfig(config *AppConfig) error {
defer file.Close()
enc := yaml.NewEncoder(file)
enc.SetIndent(2)
if err := enc.Encode(config); err != nil {
if err := enc.Encode(cfg); err != nil {
return fmt.Errorf("写入配置文件失败: %w", err)
}
return enc.Close()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,376 @@
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import {
WarningOutlined,
DownloadOutlined,
FolderOpenOutlined,
CheckCircleOutlined,
LoadingOutlined,
CloseCircleOutlined,
} from '@ant-design/icons-vue'
import { AppService } from '../../bindings/github.com/zhuy1228/GitPilot/internal/app'
import { Events } from '@wailsio/runtime'
const emit = defineEmits(['ready'])
const checking = ref(true)
const gitInstalled = ref(false)
const gitVersion = ref('')
const gitPath = ref('')
// 安装相关
const installDir = ref('C:\\Program Files\\Git')
const installing = ref(false)
const installPhase = ref('') // downloading | installing | done | error
const installPercent = ref(0)
const installMessage = ref('')
let unsubscribe = null
onMounted(async () => {
// 监听安装进度事件
unsubscribe = Events.On('git-install-progress', (event) => {
const data = event.data?.[0] || event.data
if (data) {
installPhase.value = data.phase
installPercent.value = data.percent
installMessage.value = data.message
if (data.phase === 'done') {
installing.value = false
gitInstalled.value = true
setTimeout(() => emit('ready'), 1500)
} else if (data.phase === 'error') {
installing.value = false
}
}
})
// 检查 Git
try {
const status = await AppService.CheckGitInstalled()
gitInstalled.value = status.installed
gitVersion.value = status.version || ''
gitPath.value = status.path || ''
if (status.installed) {
emit('ready')
}
} catch (e) {
console.error('检查 Git 失败:', e)
gitInstalled.value = false
} finally {
checking.value = false
}
})
onBeforeUnmount(() => {
if (unsubscribe) unsubscribe()
})
async function selectInstallDir() {
try {
const dir = await AppService.SelectGitInstallDir()
if (dir) installDir.value = dir
} catch (e) {
console.error('选择目录失败:', e)
}
}
async function startInstall() {
installing.value = true
installPhase.value = 'downloading'
installPercent.value = 0
installMessage.value = '准备下载...'
try {
await AppService.InstallGit(installDir.value)
} catch (e) {
console.error('安装 Git 失败:', e)
installPhase.value = 'error'
installMessage.value = '安装失败: ' + String(e)
installing.value = false
}
}
</script>
<template>
<!-- 检查中 -->
<div v-if="checking" class="git-check-overlay">
<div class="git-check-card">
<LoadingOutlined :style="{ fontSize: '32px', color: 'var(--accent, #89b4fa)' }" spin />
<div class="git-check-title">正在检查 Git 环境...</div>
</div>
</div>
<!-- Git 未安装 -->
<div v-else-if="!gitInstalled" class="git-check-overlay">
<div class="git-check-card install-card">
<div class="git-check-icon">
<WarningOutlined :style="{ fontSize: '48px', color: '#fab387' }" />
</div>
<div class="git-check-title">未检测到 Git</div>
<div class="git-check-desc">GitPilot 需要 Git 才能正常工作请安装 Git 后使用</div>
<!-- 安装进度 -->
<template v-if="installing || installPhase === 'done'">
<div class="install-progress">
<div v-if="installPhase === 'downloading'" class="progress-section">
<div class="progress-bar-bg">
<div class="progress-bar-fill" :style="{ width: installPercent + '%' }"></div>
</div>
<div class="progress-text">
<DownloadOutlined /> {{ installMessage }}
</div>
</div>
<div v-else-if="installPhase === 'installing'" class="progress-section">
<div class="progress-bar-bg">
<div class="progress-bar-fill installing-anim" style="width: 100%"></div>
</div>
<div class="progress-text">
<LoadingOutlined spin /> {{ installMessage }}
</div>
</div>
<div v-else-if="installPhase === 'done'" class="progress-section done">
<CheckCircleOutlined :style="{ fontSize: '24px', color: '#a6e3a1' }" />
<div class="progress-text success">{{ installMessage }}</div>
</div>
<div v-else-if="installPhase === 'error'" class="progress-section error">
<CloseCircleOutlined :style="{ fontSize: '24px', color: '#f38ba8' }" />
<div class="progress-text error-text">{{ installMessage }}</div>
</div>
</div>
</template>
<!-- 安装表单 -->
<template v-else>
<div class="install-form">
<div class="install-field">
<label>安装路径</label>
<div class="install-dir-input">
<input
v-model="installDir"
class="dir-input"
placeholder="C:\Program Files\Git"
/>
<button class="dir-btn" @click="selectInstallDir" title="选择目录">
<FolderOpenOutlined />
</button>
</div>
</div>
</div>
<div class="install-actions">
<button class="install-btn primary" @click="startInstall">
<DownloadOutlined /> 自动下载并安装 Git
</button>
</div>
<div class="install-hint">
将从 GitHub 下载 Git for Windows 安装包 65MB需保持网络连接
</div>
</template>
</div>
</div>
</template>
<style scoped>
.git-check-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary, #1e1e2e);
}
.git-check-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 40px;
max-width: 480px;
width: 100%;
}
.install-card {
background: var(--bg-secondary, #181825);
border: 1px solid var(--border-color, #313244);
border-radius: 12px;
padding: 40px 36px;
}
.git-check-icon {
margin-bottom: 4px;
}
.git-check-title {
font-size: 20px;
font-weight: 700;
color: var(--text-primary, #cdd6f4);
}
.git-check-desc {
font-size: 14px;
color: var(--text-muted, #6c7086);
text-align: center;
line-height: 1.6;
}
.install-form {
width: 100%;
margin-top: 8px;
}
.install-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.install-field label {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary, #a6adc8);
}
.install-dir-input {
display: flex;
gap: 6px;
}
.dir-input {
flex: 1;
padding: 8px 12px;
background: var(--bg-primary, #1e1e2e);
border: 1px solid var(--border-color, #313244);
border-radius: 6px;
color: var(--text-primary, #cdd6f4);
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
outline: none;
transition: border-color 0.15s;
}
.dir-input:focus {
border-color: var(--accent, #89b4fa);
}
.dir-btn {
padding: 8px 12px;
background: var(--bg-primary, #1e1e2e);
border: 1px solid var(--border-color, #313244);
border-radius: 6px;
color: var(--accent, #89b4fa);
cursor: pointer;
font-size: 15px;
display: flex;
align-items: center;
transition: all 0.15s;
}
.dir-btn:hover {
background: var(--bg-hover, #313244);
}
.install-actions {
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 4px;
}
.install-btn {
padding: 10px 20px;
border-radius: 8px;
border: 1px solid var(--border-color, #313244);
font-size: 14px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.15s;
}
.install-btn.primary {
background: var(--accent, #89b4fa);
color: #1e1e2e;
border-color: var(--accent, #89b4fa);
}
.install-btn.primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.install-hint {
font-size: 12px;
color: var(--text-muted, #6c7086);
text-align: center;
line-height: 1.5;
}
/* 安装进度 */
.install-progress {
width: 100%;
margin-top: 8px;
}
.progress-section {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
}
.progress-section.done,
.progress-section.error {
gap: 12px;
}
.progress-bar-bg {
width: 100%;
height: 6px;
background: var(--bg-primary, #1e1e2e);
border-radius: 3px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: var(--accent, #89b4fa);
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-bar-fill.installing-anim {
background: linear-gradient(90deg, var(--accent, #89b4fa) 0%, #b4befe 50%, var(--accent, #89b4fa) 100%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.progress-text {
font-size: 13px;
color: var(--text-secondary, #a6adc8);
display: flex;
align-items: center;
gap: 6px;
}
.progress-text.success {
color: #a6e3a1;
font-weight: 600;
}
.progress-text.error-text {
color: #f38ba8;
font-size: 12px;
text-align: center;
word-break: break-all;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,11 @@
import { ref, onBeforeUnmount } from 'vue'
const selectedProject = ref(null)
const gitReady = ref(false)
function onGitReady() {
gitReady.value = true
}
function onSelectProject(project) {
selectedProject.value = project
@@ -43,7 +48,11 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="main-layout">
<!-- Git 环境检查 -->
<GitCheck v-if="!gitReady" @ready="onGitReady" />
<!-- 主界面 -->
<div v-else class="main-layout">
<Sidebar :selected-project="selectedProject" :style="{ width: sidebarWidth + 'px' }" @select-project="onSelectProject" />
<div class="resize-handle" @mousedown="startSidebarResize"></div>
<ContentArea :project="selectedProject" />

View File

@@ -0,0 +1,8 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
Group,
Settings
} from "./models.js";

View File

@@ -0,0 +1,91 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
/**
* Group 项目分组
*/
export class Group {
/**
* Creates a new Group instance.
* @param {Partial<Group>} [$$source = {}] - The source object to create the Group.
*/
constructor($$source = {}) {
if (!("Name" in $$source)) {
/**
* @member
* @type {string}
*/
this["Name"] = "";
}
if (!("Icon" in $$source)) {
/**
* 可选图标标识
* @member
* @type {string}
*/
this["Icon"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new Group instance from a string or object.
* @param {any} [$$source = {}]
* @returns {Group}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Group(/** @type {Partial<Group>} */($$parsedSource));
}
}
/**
* Settings 应用设置
*/
export class Settings {
/**
* Creates a new Settings instance.
* @param {Partial<Settings>} [$$source = {}] - The source object to create the Settings.
*/
constructor($$source = {}) {
if (!("Concurrency" in $$source)) {
/**
* @member
* @type {number}
*/
this["Concurrency"] = 0;
}
if (!("NetworkCheck" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["NetworkCheck"] = false;
}
if (!("LogLevel" in $$source)) {
/**
* @member
* @type {string}
*/
this["LogLevel"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new Settings instance from a string or object.
* @param {any} [$$source = {}]
* @returns {Settings}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Settings(/** @type {Partial<Settings>} */($$parsedSource));
}
}

View File

@@ -14,42 +14,121 @@ import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Cr
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../../../wailsapp/wails/v3/pkg/application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as config$0 from "../../config/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* AddPlatform 添加新平台
* @param {string} name
* @param {string} baseURL
* @returns {$CancellablePromise<void>}
*/
export function AddPlatform(name, baseURL) {
return $Call.ByID(526003212, name, baseURL);
}
/**
* AddProject 添加项目到指定平台/用户下
* @param {string} platform
* @param {string} username
* @param {string} name
* AbortMerge 中止合并
* @param {string} path
* @returns {$CancellablePromise<void>}
*/
export function AddProject(platform, username, name, path) {
return $Call.ByID(2299402672, platform, username, name, path);
export function AbortMerge(path) {
return $Call.ByID(3035150106, path);
}
/**
* AddUser 添加用户到指定平台
* AddCredential 添加凭证
* @param {string} platform
* @param {string} baseURL
* @param {string} username
* @param {string} token
* @returns {$CancellablePromise<void>}
*/
export function AddUser(platform, username, token) {
return $Call.ByID(2264426704, platform, username, token);
export function AddCredential(platform, baseURL, username, token) {
return $Call.ByID(1627284202, platform, baseURL, username, token);
}
/**
* AddGroup 添加分组
* @param {string} name
* @param {string} icon
* @returns {$CancellablePromise<void>}
*/
export function AddGroup(name, icon) {
return $Call.ByID(4237035530, name, icon);
}
/**
* AddProject 添加项目
* @param {string} name
* @param {string} path
* @param {string} group
* @returns {$CancellablePromise<void>}
*/
export function AddProject(name, path, group) {
return $Call.ByID(2299402672, name, path, group);
}
/**
* AddRemote 添加远程仓库
* @param {string} path
* @param {string} name
* @param {string} url
* @returns {$CancellablePromise<void>}
*/
export function AddRemote(path, name, url) {
return $Call.ByID(3697916441, path, name, url);
}
/**
* BatchPull 批量拉取指定项目
* @param {string[]} paths
* @param {string} remote
* @returns {$CancellablePromise<$models.BatchPullResult[]>}
*/
export function BatchPull(paths, remote) {
return $Call.ByID(758996647, paths, remote).then(/** @type {($result: any) => any} */(($result) => {
return $$createType1($result);
}));
}
/**
* BatchPush 批量推送指定项目
* @param {string[]} paths
* @param {string} remote
* @returns {$CancellablePromise<$models.BatchPullResult[]>}
*/
export function BatchPush(paths, remote) {
return $Call.ByID(794082076, paths, remote).then(/** @type {($result: any) => any} */(($result) => {
return $$createType1($result);
}));
}
/**
* CheckGitInstalled 检查 Git 是否已安装
* @returns {$CancellablePromise<$models.GitStatus>}
*/
export function CheckGitInstalled() {
return $Call.ByID(464620426).then(/** @type {($result: any) => any} */(($result) => {
return $$createType2($result);
}));
}
/**
* CheckoutRemoteBranch 检出远程分支到本地
* @param {string} path
* @param {string} remoteBranch
* @returns {$CancellablePromise<void>}
*/
export function CheckoutRemoteBranch(path, remoteBranch) {
return $Call.ByID(3682237522, path, remoteBranch);
}
/**
* CloneProject 克隆远程仓库到本地目录,并添加到项目列表
* @param {string} repoURL
* @param {string} parentDir
* @param {string} name
* @param {string} group
* @returns {$CancellablePromise<void>}
*/
export function CloneProject(repoURL, parentDir, name, group) {
return $Call.ByID(2347019414, repoURL, parentDir, name, group);
}
/**
@@ -62,6 +141,16 @@ export function CommitChanges(path, message) {
return $Call.ByID(921984546, path, message);
}
/**
* CreateBranch 创建新分支
* @param {string} path
* @param {string} name
* @returns {$CancellablePromise<void>}
*/
export function CreateBranch(path, name) {
return $Call.ByID(1422152924, path, name);
}
/**
* CreateTag 创建标签
* @param {string} path
@@ -73,14 +162,37 @@ export function CreateTag(path, name, message) {
return $Call.ByID(4209616956, path, name, message);
}
/**
* DeleteBranch 删除本地分支
* @param {string} path
* @param {string} name
* @param {boolean} force
* @returns {$CancellablePromise<void>}
*/
export function DeleteBranch(path, name, force) {
return $Call.ByID(580272035, path, name, force);
}
/**
* DeleteRemoteBranch 删除远程分支
* @param {string} path
* @param {string} branch
* @param {string} remote
* @returns {$CancellablePromise<void>}
*/
export function DeleteRemoteBranch(path, branch, remote) {
return $Call.ByID(2626609049, path, branch, remote);
}
/**
* DeleteTag 删除标签(本地+远程)
* @param {string} path
* @param {string} name
* @param {string} remote
* @returns {$CancellablePromise<void>}
*/
export function DeleteTag(path, name) {
return $Call.ByID(873653241, path, name);
export function DeleteTag(path, name, remote) {
return $Call.ByID(873653241, path, name, remote);
}
/**
@@ -94,12 +206,33 @@ export function DiscardFiles(path, files) {
}
/**
* FetchProject 拉取远程信息
* FetchProject 拉取远程信息(指定 remote空则 fetch --all
* @param {string} path
* @param {string} remote
* @returns {$CancellablePromise<string>}
*/
export function FetchProject(path) {
return $Call.ByID(3541106829, path);
export function FetchProject(path, remote) {
return $Call.ByID(3541106829, path, remote);
}
/**
* GetAllProjectOverview 获取所有项目的概览状态
* @returns {$CancellablePromise<$models.ProjectOverview[]>}
*/
export function GetAllProjectOverview() {
return $Call.ByID(2453130151).then(/** @type {($result: any) => any} */(($result) => {
return $$createType4($result);
}));
}
/**
* GetAppSettings 获取应用设置
* @returns {$CancellablePromise<config$0.Settings | null>}
*/
export function GetAppSettings() {
return $Call.ByID(428589026).then(/** @type {($result: any) => any} */(($result) => {
return $$createType6($result);
}));
}
/**
@@ -109,7 +242,7 @@ export function FetchProject(path) {
*/
export function GetBranches(path) {
return $Call.ByID(1686190192, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType1($result);
return $$createType8($result);
}));
}
@@ -142,7 +275,7 @@ export function GetCommitFileDiff(path, hash, filePath) {
*/
export function GetCommitFiles(path, hash) {
return $Call.ByID(2420707876, path, hash).then(/** @type {($result: any) => any} */(($result) => {
return $$createType3($result);
return $$createType10($result);
}));
}
@@ -154,7 +287,38 @@ export function GetCommitFiles(path, hash) {
*/
export function GetCommitLog(path, count) {
return $Call.ByID(1281870789, path, count).then(/** @type {($result: any) => any} */(($result) => {
return $$createType5($result);
return $$createType12($result);
}));
}
/**
* GetConflictFileContent 获取冲突文件内容(包含冲突标记)
* @param {string} projectPath
* @param {string} filePath
* @returns {$CancellablePromise<string>}
*/
export function GetConflictFileContent(projectPath, filePath) {
return $Call.ByID(298498517, projectPath, filePath);
}
/**
* GetConflictFiles 获取冲突文件列表
* @param {string} path
* @returns {$CancellablePromise<$models.ConflictFileInfo[]>}
*/
export function GetConflictFiles(path) {
return $Call.ByID(2446806137, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType14($result);
}));
}
/**
* GetCredentials 获取所有凭证
* @returns {$CancellablePromise<$models.CredentialInfo[]>}
*/
export function GetCredentials() {
return $Call.ByID(4214051290).then(/** @type {($result: any) => any} */(($result) => {
return $$createType16($result);
}));
}
@@ -189,13 +353,22 @@ export function GetFileDiffStaged(projectPath, filePath) {
}
/**
* GetPlatformInfo 获取平台信息
* @param {string} name
* @returns {$CancellablePromise<$models.PlatformInfo | null>}
* GetGitGlobalConfig 获取 git 全局配置
* @returns {$CancellablePromise<$models.GitConfig | null>}
*/
export function GetPlatformInfo(name) {
return $Call.ByID(2668095547, name).then(/** @type {($result: any) => any} */(($result) => {
return $$createType7($result);
export function GetGitGlobalConfig() {
return $Call.ByID(154811497).then(/** @type {($result: any) => any} */(($result) => {
return $$createType18($result);
}));
}
/**
* GetGroups 获取所有分组
* @returns {$CancellablePromise<config$0.Group[]>}
*/
export function GetGroups() {
return $Call.ByID(1600884836).then(/** @type {($result: any) => any} */(($result) => {
return $$createType20($result);
}));
}
@@ -206,10 +379,19 @@ export function GetPlatformInfo(name) {
*/
export function GetProjectChangedFiles(path) {
return $Call.ByID(2302591462, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType9($result);
return $$createType22($result);
}));
}
/**
* GetProjectProxy 获取项目代理设置nil 表示跟随全局)
* @param {string} path
* @returns {$CancellablePromise<boolean | null>}
*/
export function GetProjectProxy(path) {
return $Call.ByID(2515497911, path);
}
/**
* GetProjectStatus 获取项目 git 状态
* @param {string} path
@@ -217,17 +399,51 @@ export function GetProjectChangedFiles(path) {
*/
export function GetProjectStatus(path) {
return $Call.ByID(3451089027, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType11($result);
return $$createType24($result);
}));
}
/**
* GetProjectTree 获取项目树,供前端侧边栏渲染
* GetProjectTree 获取项目树,按分组组织
* @returns {$CancellablePromise<$models.TreeNode[]>}
*/
export function GetProjectTree() {
return $Call.ByID(651189689).then(/** @type {($result: any) => any} */(($result) => {
return $$createType13($result);
return $$createType26($result);
}));
}
/**
* GetRemoteBranches 获取远程分支列表
* @param {string} path
* @param {string} remote
* @returns {$CancellablePromise<$models.BranchInfo[]>}
*/
export function GetRemoteBranches(path, remote) {
return $Call.ByID(994796370, path, remote).then(/** @type {($result: any) => any} */(($result) => {
return $$createType8($result);
}));
}
/**
* GetRemotes 获取项目所有远程仓库列表
* @param {string} path
* @returns {$CancellablePromise<$models.RemoteItem[]>}
*/
export function GetRemotes(path) {
return $Call.ByID(975520027, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType28($result);
}));
}
/**
* GetStashList 获取贮藏列表
* @param {string} path
* @returns {$CancellablePromise<$models.StashInfo[]>}
*/
export function GetStashList(path) {
return $Call.ByID(1948257945, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType30($result);
}));
}
@@ -238,78 +454,139 @@ export function GetProjectTree() {
*/
export function GetTags(path) {
return $Call.ByID(3979169621, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType15($result);
return $$createType32($result);
}));
}
/**
* GetUserInfo 获取用户信息
* @param {string} platform
* @param {string} username
* @returns {$CancellablePromise<$models.UserInfo | null>}
* InstallGit 下载并安装 Git
* installDir: 用户选择的安装目录Windows 有效)
* @param {string} installDir
* @returns {$CancellablePromise<void>}
*/
export function GetUserInfo(platform, username) {
return $Call.ByID(2674655707, platform, username).then(/** @type {($result: any) => any} */(($result) => {
return $$createType17($result);
}));
export function InstallGit(installDir) {
return $Call.ByID(1052641569, installDir);
}
/**
* PullProject 拉取项目(当前分支)
* IsMerging 检查是否处于合并状态
* @param {string} path
* @returns {$CancellablePromise<string>}
* @returns {$CancellablePromise<boolean>}
*/
export function PullProject(path) {
return $Call.ByID(4145813996, path);
export function IsMerging(path) {
return $Call.ByID(693809703, path);
}
/**
* PushProject 推送项目(当前分支
* MergeBranch 合并指定分支到当前分支
* @param {string} path
* @param {string} branch
* @returns {$CancellablePromise<string>}
*/
export function PushProject(path) {
return $Call.ByID(3887901113, path);
export function MergeBranch(path, branch) {
return $Call.ByID(278161076, path, branch);
}
/**
* MoveProjectToGroup 移动项目到指定分组
* @param {string} path
* @param {string} group
* @returns {$CancellablePromise<void>}
*/
export function MoveProjectToGroup(path, group) {
return $Call.ByID(1699187666, path, group);
}
/**
* PullProject 拉取项目(当前分支,指定 remote
* @param {string} path
* @param {string} remote
* @returns {$CancellablePromise<string>}
*/
export function PullProject(path, remote) {
return $Call.ByID(4145813996, path, remote);
}
/**
* PushProject 推送项目(当前分支,指定 remote
* @param {string} path
* @param {string} remote
* @returns {$CancellablePromise<string>}
*/
export function PushProject(path, remote) {
return $Call.ByID(3887901113, path, remote);
}
/**
* PushTag 推送标签到远程
* @param {string} path
* @param {string} name
* @param {string} remote
* @returns {$CancellablePromise<void>}
*/
export function PushTag(path, name) {
return $Call.ByID(1424810524, path, name);
export function PushTag(path, name, remote) {
return $Call.ByID(1424810524, path, name, remote);
}
/**
* RemovePlatform 删除平台
* @param {string} name
* @returns {$CancellablePromise<void>}
* PushTagToAllRemotes 一键推送标签到所有远程仓库
* @param {string} path
* @param {string} tagName
* @returns {$CancellablePromise<$models.PushAllResult[]>}
*/
export function RemovePlatform(name) {
return $Call.ByID(2222448051, name);
export function PushTagToAllRemotes(path, tagName) {
return $Call.ByID(2343581171, path, tagName).then(/** @type {($result: any) => any} */(($result) => {
return $$createType34($result);
}));
}
/**
* RemoveProject 从指定平台/用户下删除项目
* @param {string} platform
* @param {string} username
* @param {string} name
* @returns {$CancellablePromise<void>}
* PushToAllRemotes 一键推送到所有远程仓库(当前分支)
* @param {string} path
* @returns {$CancellablePromise<$models.PushAllResult[]>}
*/
export function RemoveProject(platform, username, name) {
return $Call.ByID(2748109581, platform, username, name);
export function PushToAllRemotes(path) {
return $Call.ByID(1027392731, path).then(/** @type {($result: any) => any} */(($result) => {
return $$createType34($result);
}));
}
/**
* RemoveUser 从平台删除用户
* RemoveCredential 删除凭证
* @param {string} platform
* @param {string} username
* @returns {$CancellablePromise<void>}
*/
export function RemoveUser(platform, username) {
return $Call.ByID(1409517275, platform, username);
export function RemoveCredential(platform, username) {
return $Call.ByID(414538393, platform, username);
}
/**
* RemoveGroup 删除分组(分组下的项目变为"未分组"
* @param {string} name
* @returns {$CancellablePromise<void>}
*/
export function RemoveGroup(name) {
return $Call.ByID(3268966203, name);
}
/**
* RemoveProject 删除项目(按路径匹配)
* @param {string} path
* @returns {$CancellablePromise<void>}
*/
export function RemoveProject(path) {
return $Call.ByID(2748109581, path);
}
/**
* RemoveRemote 删除远程仓库
* @param {string} path
* @param {string} name
* @returns {$CancellablePromise<void>}
*/
export function RemoveRemote(path, name) {
return $Call.ByID(2915623022, path, name);
}
/**
@@ -324,6 +601,16 @@ export function ResetProject(path, hash, mode) {
return $Call.ByID(2061148684, path, hash, mode);
}
/**
* ResolveConflictFile 将冲突文件标记为已解决
* @param {string} path
* @param {string[]} files
* @returns {$CancellablePromise<void>}
*/
export function ResolveConflictFile(path, files) {
return $Call.ByID(1893119072, path, files);
}
/**
* RevertCommit 撤回指定提交(生成一个反向提交)
* @param {string} path
@@ -334,6 +621,31 @@ export function RevertCommit(path, hash) {
return $Call.ByID(1334788539, path, hash);
}
/**
* SaveConflictFile 保存冲突文件内容(手动解决冲突后保存)
* @param {string} projectPath
* @param {string} filePath
* @param {string} content
* @returns {$CancellablePromise<void>}
*/
export function SaveConflictFile(projectPath, filePath, content) {
return $Call.ByID(3340289161, projectPath, filePath, content);
}
/**
* SearchCommitLog 搜索提交历史
* @param {string} path
* @param {string} keyword
* @param {string} author
* @param {number} maxCount
* @returns {$CancellablePromise<$models.CommitLog[]>}
*/
export function SearchCommitLog(path, keyword, author, maxCount) {
return $Call.ByID(2874450917, path, keyword, author, maxCount).then(/** @type {($result: any) => any} */(($result) => {
return $$createType12($result);
}));
}
/**
* SelectDirectory 打开系统文件夹选择器,返回选中的路径
* @returns {$CancellablePromise<string>}
@@ -342,6 +654,14 @@ export function SelectDirectory() {
return $Call.ByID(2318416763);
}
/**
* SelectGitInstallDir 打开文件夹选择器让用户选择 Git 安装路径
* @returns {$CancellablePromise<string>}
*/
export function SelectGitInstallDir() {
return $Call.ByID(2941074074);
}
/**
* @param {application$0.App | null} app
* @returns {$CancellablePromise<void>}
@@ -350,6 +670,27 @@ export function SetApplication(app) {
return $Call.ByID(383695022, app);
}
/**
* SetGitGlobalConfig 设置 git 全局配置
* @param {string} name
* @param {string} email
* @returns {$CancellablePromise<void>}
*/
export function SetGitGlobalConfig(name, email) {
return $Call.ByID(3875064981, name, email);
}
/**
* SetProjectProxy 设置项目代理开关
* useProxy: true=强制启用, false=强制禁用, nil(传空)=跟随全局
* @param {string} path
* @param {boolean | null} useProxy
* @returns {$CancellablePromise<void>}
*/
export function SetProjectProxy(path, useProxy) {
return $Call.ByID(4094650651, path, useProxy);
}
/**
* StageAll 暂存所有变更文件
* @param {string} path
@@ -369,6 +710,46 @@ export function StageFiles(path, files) {
return $Call.ByID(4064506881, path, files);
}
/**
* StashApply 应用贮藏(不删除)
* @param {string} path
* @param {number} index
* @returns {$CancellablePromise<void>}
*/
export function StashApply(path, index) {
return $Call.ByID(721583959, path, index);
}
/**
* StashDrop 删除贮藏
* @param {string} path
* @param {number} index
* @returns {$CancellablePromise<void>}
*/
export function StashDrop(path, index) {
return $Call.ByID(3821556536, path, index);
}
/**
* StashPop 应用贮藏并删除
* @param {string} path
* @param {number} index
* @returns {$CancellablePromise<void>}
*/
export function StashPop(path, index) {
return $Call.ByID(4117964460, path, index);
}
/**
* StashSave 保存当前变更到贮藏
* @param {string} path
* @param {string} message
* @returns {$CancellablePromise<void>}
*/
export function StashSave(path, message) {
return $Call.ByID(1178263140, path, message);
}
/**
* SwitchBranch 切换分支
* @param {string} path
@@ -399,43 +780,70 @@ export function UnstageFiles(path, files) {
}
/**
* UpdatePlatform 修改平台信息base_url
* @param {string} name
* @param {string} baseURL
* UpdateAppSettings 更新应用设置
* @param {string} logLevel
* @returns {$CancellablePromise<void>}
*/
export function UpdatePlatform(name, baseURL) {
return $Call.ByID(3258014550, name, baseURL);
export function UpdateAppSettings(logLevel) {
return $Call.ByID(718820989, logLevel);
}
/**
* UpdateUser 修改用户信息用户名、token
* UpdateCredential 更新凭证
* @param {string} platform
* @param {string} oldUsername
* @param {string} newUsername
* @param {string} username
* @param {string} baseURL
* @param {string} token
* @returns {$CancellablePromise<void>}
*/
export function UpdateUser(platform, oldUsername, newUsername, token) {
return $Call.ByID(1631729342, platform, oldUsername, newUsername, token);
export function UpdateCredential(platform, username, baseURL, token) {
return $Call.ByID(2448329924, platform, username, baseURL, token);
}
/**
* UpdateGroup 修改分组
* @param {string} oldName
* @param {string} newName
* @param {string} icon
* @returns {$CancellablePromise<void>}
*/
export function UpdateGroup(oldName, newName, icon) {
return $Call.ByID(1322206248, oldName, newName, icon);
}
// Private type creation functions
const $$createType0 = $models.BranchInfo.createFrom;
const $$createType0 = $models.BatchPullResult.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = $models.CommitFileInfo.createFrom;
const $$createType3 = $Create.Array($$createType2);
const $$createType4 = $models.CommitLog.createFrom;
const $$createType5 = $Create.Array($$createType4);
const $$createType6 = $models.PlatformInfo.createFrom;
const $$createType7 = $Create.Nullable($$createType6);
const $$createType8 = $models.FileInfo.createFrom;
const $$createType9 = $Create.Array($$createType8);
const $$createType10 = $models.ProjectStatus.createFrom;
const $$createType11 = $Create.Nullable($$createType10);
const $$createType12 = $models.TreeNode.createFrom;
const $$createType13 = $Create.Array($$createType12);
const $$createType14 = $models.TagInfo.createFrom;
const $$createType15 = $Create.Array($$createType14);
const $$createType16 = $models.UserInfo.createFrom;
const $$createType17 = $Create.Nullable($$createType16);
const $$createType2 = $models.GitStatus.createFrom;
const $$createType3 = $models.ProjectOverview.createFrom;
const $$createType4 = $Create.Array($$createType3);
const $$createType5 = config$0.Settings.createFrom;
const $$createType6 = $Create.Nullable($$createType5);
const $$createType7 = $models.BranchInfo.createFrom;
const $$createType8 = $Create.Array($$createType7);
const $$createType9 = $models.CommitFileInfo.createFrom;
const $$createType10 = $Create.Array($$createType9);
const $$createType11 = $models.CommitLog.createFrom;
const $$createType12 = $Create.Array($$createType11);
const $$createType13 = $models.ConflictFileInfo.createFrom;
const $$createType14 = $Create.Array($$createType13);
const $$createType15 = $models.CredentialInfo.createFrom;
const $$createType16 = $Create.Array($$createType15);
const $$createType17 = $models.GitConfig.createFrom;
const $$createType18 = $Create.Nullable($$createType17);
const $$createType19 = config$0.Group.createFrom;
const $$createType20 = $Create.Array($$createType19);
const $$createType21 = $models.FileInfo.createFrom;
const $$createType22 = $Create.Array($$createType21);
const $$createType23 = $models.ProjectStatus.createFrom;
const $$createType24 = $Create.Nullable($$createType23);
const $$createType25 = $models.TreeNode.createFrom;
const $$createType26 = $Create.Array($$createType25);
const $$createType27 = $models.RemoteItem.createFrom;
const $$createType28 = $Create.Array($$createType27);
const $$createType29 = $models.StashInfo.createFrom;
const $$createType30 = $Create.Array($$createType29);
const $$createType31 = $models.TagInfo.createFrom;
const $$createType32 = $Create.Array($$createType31);
const $$createType33 = $models.PushAllResult.createFrom;
const $$createType34 = $Create.Array($$createType33);

View File

@@ -8,13 +8,20 @@ export {
};
export {
BatchPullResult,
BranchInfo,
CommitFileInfo,
CommitLog,
ConflictFileInfo,
CredentialInfo,
FileInfo,
PlatformInfo,
GitConfig,
GitStatus,
ProjectOverview,
ProjectStatus,
PushAllResult,
RemoteItem,
StashInfo,
TagInfo,
TreeNode,
UserInfo
TreeNode
} from "./models.js";

View File

@@ -6,6 +6,58 @@
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
/**
* BatchPullResult 批量 pull 结果
*/
export class BatchPullResult {
/**
* Creates a new BatchPullResult instance.
* @param {Partial<BatchPullResult>} [$$source = {}] - The source object to create the BatchPullResult.
*/
constructor($$source = {}) {
if (!("name" in $$source)) {
/**
* @member
* @type {string}
*/
this["name"] = "";
}
if (!("path" in $$source)) {
/**
* @member
* @type {string}
*/
this["path"] = "";
}
if (!("success" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["success"] = false;
}
if (!("message" in $$source)) {
/**
* @member
* @type {string}
*/
this["message"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new BatchPullResult instance from a string or object.
* @param {any} [$$source = {}]
* @returns {BatchPullResult}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new BatchPullResult(/** @type {Partial<BatchPullResult>} */($$parsedSource));
}
}
/**
* BranchInfo 分支信息
*/
@@ -155,6 +207,89 @@ export class CommitLog {
}
}
/**
* ConflictFileInfo 冲突文件信息
*/
export class ConflictFileInfo {
/**
* Creates a new ConflictFileInfo instance.
* @param {Partial<ConflictFileInfo>} [$$source = {}] - The source object to create the ConflictFileInfo.
*/
constructor($$source = {}) {
if (!("filePath" in $$source)) {
/**
* @member
* @type {string}
*/
this["filePath"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new ConflictFileInfo instance from a string or object.
* @param {any} [$$source = {}]
* @returns {ConflictFileInfo}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ConflictFileInfo(/** @type {Partial<ConflictFileInfo>} */($$parsedSource));
}
}
/**
* CredentialInfo 凭证信息(前端展示用)
*/
export class CredentialInfo {
/**
* Creates a new CredentialInfo instance.
* @param {Partial<CredentialInfo>} [$$source = {}] - The source object to create the CredentialInfo.
*/
constructor($$source = {}) {
if (!("platform" in $$source)) {
/**
* @member
* @type {string}
*/
this["platform"] = "";
}
if (!("baseUrl" in $$source)) {
/**
* @member
* @type {string}
*/
this["baseUrl"] = "";
}
if (!("username" in $$source)) {
/**
* @member
* @type {string}
*/
this["username"] = "";
}
if (!("token" in $$source)) {
/**
* @member
* @type {string}
*/
this["token"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new CredentialInfo instance from a string or object.
* @param {any} [$$source = {}]
* @returns {CredentialInfo}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new CredentialInfo(/** @type {Partial<CredentialInfo>} */($$parsedSource));
}
}
/**
* FileInfo 文件信息
*/
@@ -208,14 +343,104 @@ export class FileInfo {
}
/**
* PlatformInfo 平台信息
* GitConfig Git 全局配置
*/
export class PlatformInfo {
export class GitConfig {
/**
* Creates a new PlatformInfo instance.
* @param {Partial<PlatformInfo>} [$$source = {}] - The source object to create the PlatformInfo.
* Creates a new GitConfig instance.
* @param {Partial<GitConfig>} [$$source = {}] - The source object to create the GitConfig.
*/
constructor($$source = {}) {
if (!("userName" in $$source)) {
/**
* @member
* @type {string}
*/
this["userName"] = "";
}
if (!("userEmail" in $$source)) {
/**
* @member
* @type {string}
*/
this["userEmail"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new GitConfig instance from a string or object.
* @param {any} [$$source = {}]
* @returns {GitConfig}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new GitConfig(/** @type {Partial<GitConfig>} */($$parsedSource));
}
}
/**
* GitStatus Git 安装状态
*/
export class GitStatus {
/**
* Creates a new GitStatus instance.
* @param {Partial<GitStatus>} [$$source = {}] - The source object to create the GitStatus.
*/
constructor($$source = {}) {
if (!("installed" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["installed"] = false;
}
if (!("version" in $$source)) {
/**
* @member
* @type {string}
*/
this["version"] = "";
}
if (!("path" in $$source)) {
/**
* @member
* @type {string}
*/
this["path"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new GitStatus instance from a string or object.
* @param {any} [$$source = {}]
* @returns {GitStatus}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new GitStatus(/** @type {Partial<GitStatus>} */($$parsedSource));
}
}
/**
* ProjectOverview 项目概览信息(轻量级)
*/
export class ProjectOverview {
/**
* Creates a new ProjectOverview instance.
* @param {Partial<ProjectOverview>} [$$source = {}] - The source object to create the ProjectOverview.
*/
constructor($$source = {}) {
if (!("key" in $$source)) {
/**
* @member
* @type {string}
*/
this["key"] = "";
}
if (!("name" in $$source)) {
/**
* @member
@@ -223,25 +448,53 @@ export class PlatformInfo {
*/
this["name"] = "";
}
if (!("baseUrl" in $$source)) {
if (!("path" in $$source)) {
/**
* @member
* @type {string}
*/
this["baseUrl"] = "";
this["path"] = "";
}
if (!("branch" in $$source)) {
/**
* @member
* @type {string}
*/
this["branch"] = "";
}
if (!("hasChanges" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["hasChanges"] = false;
}
if (!("unpushed" in $$source)) {
/**
* @member
* @type {number}
*/
this["unpushed"] = 0;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["error"] = undefined;
}
Object.assign(this, $$source);
}
/**
* Creates a new PlatformInfo instance from a string or object.
* Creates a new ProjectOverview instance from a string or object.
* @param {any} [$$source = {}]
* @returns {PlatformInfo}
* @returns {ProjectOverview}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new PlatformInfo(/** @type {Partial<PlatformInfo>} */($$parsedSource));
return new ProjectOverview(/** @type {Partial<ProjectOverview>} */($$parsedSource));
}
}
@@ -268,6 +521,20 @@ export class ProjectStatus {
*/
this["remoteUrl"] = "";
}
if (!("remotes" in $$source)) {
/**
* @member
* @type {RemoteItem[]}
*/
this["remotes"] = [];
}
if (!("currentRemote" in $$source)) {
/**
* @member
* @type {string}
*/
this["currentRemote"] = "";
}
if (!("changedFiles" in $$source)) {
/**
* @member
@@ -275,6 +542,14 @@ export class ProjectStatus {
*/
this["changedFiles"] = [];
}
if (!("useProxy" in $$source)) {
/**
* nil=跟随全局, true=开启, false=关闭
* @member
* @type {boolean | null}
*/
this["useProxy"] = null;
}
Object.assign(this, $$source);
}
@@ -286,14 +561,153 @@ export class ProjectStatus {
*/
static createFrom($$source = {}) {
const $$createField2_0 = $$createType1;
const $$createField4_0 = $$createType3;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("remotes" in $$parsedSource) {
$$parsedSource["remotes"] = $$createField2_0($$parsedSource["remotes"]);
}
if ("changedFiles" in $$parsedSource) {
$$parsedSource["changedFiles"] = $$createField2_0($$parsedSource["changedFiles"]);
$$parsedSource["changedFiles"] = $$createField4_0($$parsedSource["changedFiles"]);
}
return new ProjectStatus(/** @type {Partial<ProjectStatus>} */($$parsedSource));
}
}
/**
* PushAllResult 一键多端推送结果
*/
export class PushAllResult {
/**
* Creates a new PushAllResult instance.
* @param {Partial<PushAllResult>} [$$source = {}] - The source object to create the PushAllResult.
*/
constructor($$source = {}) {
if (!("remote" in $$source)) {
/**
* @member
* @type {string}
*/
this["remote"] = "";
}
if (!("success" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["success"] = false;
}
if (!("message" in $$source)) {
/**
* @member
* @type {string}
*/
this["message"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new PushAllResult instance from a string or object.
* @param {any} [$$source = {}]
* @returns {PushAllResult}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new PushAllResult(/** @type {Partial<PushAllResult>} */($$parsedSource));
}
}
/**
* RemoteItem 远程仓库信息
*/
export class RemoteItem {
/**
* Creates a new RemoteItem instance.
* @param {Partial<RemoteItem>} [$$source = {}] - The source object to create the RemoteItem.
*/
constructor($$source = {}) {
if (!("name" in $$source)) {
/**
* @member
* @type {string}
*/
this["name"] = "";
}
if (!("url" in $$source)) {
/**
* @member
* @type {string}
*/
this["url"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new RemoteItem instance from a string or object.
* @param {any} [$$source = {}]
* @returns {RemoteItem}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new RemoteItem(/** @type {Partial<RemoteItem>} */($$parsedSource));
}
}
/**
* StashInfo 贮藏信息
*/
export class StashInfo {
/**
* Creates a new StashInfo instance.
* @param {Partial<StashInfo>} [$$source = {}] - The source object to create the StashInfo.
*/
constructor($$source = {}) {
if (!("index" in $$source)) {
/**
* @member
* @type {number}
*/
this["index"] = 0;
}
if (!("ref" in $$source)) {
/**
* @member
* @type {string}
*/
this["ref"] = "";
}
if (!("message" in $$source)) {
/**
* @member
* @type {string}
*/
this["message"] = "";
}
if (!("timestamp" in $$source)) {
/**
* @member
* @type {number}
*/
this["timestamp"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new StashInfo instance from a string or object.
* @param {any} [$$source = {}]
* @returns {StashInfo}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new StashInfo(/** @type {Partial<StashInfo>} */($$parsedSource));
}
}
/**
* TagInfo 标签信息
*/
@@ -371,7 +785,7 @@ export class TreeNode {
}
if (!("type" in $$source)) {
/**
* platform, user, project
* group, project
* @member
* @type {string}
*/
@@ -384,6 +798,13 @@ export class TreeNode {
*/
this["path"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["icon"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
@@ -401,55 +822,19 @@ export class TreeNode {
* @returns {TreeNode}
*/
static createFrom($$source = {}) {
const $$createField4_0 = $$createType3;
const $$createField5_0 = $$createType5;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("children" in $$parsedSource) {
$$parsedSource["children"] = $$createField4_0($$parsedSource["children"]);
$$parsedSource["children"] = $$createField5_0($$parsedSource["children"]);
}
return new TreeNode(/** @type {Partial<TreeNode>} */($$parsedSource));
}
}
/**
* UserInfo 用户信息
*/
export class UserInfo {
/**
* Creates a new UserInfo instance.
* @param {Partial<UserInfo>} [$$source = {}] - The source object to create the UserInfo.
*/
constructor($$source = {}) {
if (!("username" in $$source)) {
/**
* @member
* @type {string}
*/
this["username"] = "";
}
if (!("token" in $$source)) {
/**
* @member
* @type {string}
*/
this["token"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new UserInfo instance from a string or object.
* @param {any} [$$source = {}]
* @returns {UserInfo}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new UserInfo(/** @type {Partial<UserInfo>} */($$parsedSource));
}
}
// Private type creation functions
const $$createType0 = FileInfo.createFrom;
const $$createType0 = RemoteItem.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = TreeNode.createFrom;
const $$createType2 = FileInfo.createFrom;
const $$createType3 = $Create.Array($$createType2);
const $$createType4 = TreeNode.createFrom;
const $$createType5 = $Create.Array($$createType4);

241
internal/app/gitsetup.go Normal file
View File

@@ -0,0 +1,241 @@
package app
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/zhuy1228/GitPilot/internal/git"
)
// GitStatus Git 安装状态
type GitStatus struct {
Installed bool `json:"installed"`
Version string `json:"version"`
Path string `json:"path"`
}
// GitInstallProgress 安装进度
type GitInstallProgress struct {
Phase string `json:"phase"` // downloading, installing, done, error
Percent float64 `json:"percent"` // 下载百分比 0-100
Message string `json:"message"`
}
// CheckGitInstalled 检查 Git 是否已安装
func (s *AppService) CheckGitInstalled() GitStatus {
path, err := exec.LookPath("git")
if err != nil {
log.Println("Git 未安装:", err)
return GitStatus{Installed: false}
}
// 获取版本号
cmd := exec.Command("git", "--version")
hideWindowCmd(cmd)
output, err := cmd.Output()
if err != nil {
log.Println("获取 Git 版本失败:", err)
return GitStatus{Installed: true, Path: path}
}
version := strings.TrimSpace(string(output))
// "git version 2.43.0.windows.1" -> "2.43.0"
version = strings.TrimPrefix(version, "git version ")
if idx := strings.Index(version, ".windows"); idx > 0 {
version = version[:idx]
}
return GitStatus{
Installed: true,
Version: version,
Path: path,
}
}
// SelectGitInstallDir 打开文件夹选择器让用户选择 Git 安装路径
func (s *AppService) SelectGitInstallDir() (string, error) {
if s.app == nil {
return "", fmt.Errorf("应用未初始化")
}
path, err := s.app.Dialog.OpenFile().
CanChooseDirectories(true).
CanChooseFiles(false).
SetTitle("选择 Git 安装路径").
PromptForSingleSelection()
if err != nil {
return "", fmt.Errorf("打开文件夹选择器失败: %w", err)
}
return path, nil
}
// getGitDownloadURL 根据平台返回 Git 下载地址
func getGitDownloadURL() string {
switch runtime.GOOS {
case "windows":
if runtime.GOARCH == "arm64" {
return "https://github.com/git-for-windows/git/releases/download/v2.49.0.windows.1/Git-2.49.0-arm64.exe"
}
return "https://github.com/git-for-windows/git/releases/download/v2.49.0.windows.1/Git-2.49.0-64-bit.exe"
case "darwin":
return "" // macOS 建议使用 brew 或 Xcode CLI
default:
return "" // Linux 建议使用包管理器
}
}
// InstallGit 下载并安装 Git
// installDir: 用户选择的安装目录Windows 有效)
func (s *AppService) InstallGit(installDir string) error {
if runtime.GOOS != "windows" {
return fmt.Errorf("自动安装仅支持 Windows请使用系统包管理器安装 Git")
}
url := getGitDownloadURL()
if url == "" {
return fmt.Errorf("无法获取 Git 下载地址")
}
// 1. 下载安装包
s.emitInstallProgress("downloading", 0, "正在下载 Git 安装包...")
log.Println("开始下载 Git:", url)
tmpDir := os.TempDir()
installerPath := filepath.Join(tmpDir, "Git-Installer.exe")
err := s.downloadFile(url, installerPath)
if err != nil {
s.emitInstallProgress("error", 0, "下载失败: "+err.Error())
return fmt.Errorf("下载 Git 安装包失败: %w", err)
}
defer os.Remove(installerPath)
// 2. 静默安装
s.emitInstallProgress("installing", 100, "正在安装 Git请稍候...")
log.Println("开始安装 Git 到:", installDir)
// Git for Windows 静默安装参数
args := []string{
"/VERYSILENT",
"/NORESTART",
"/NOCANCEL",
"/SP-",
"/CLOSEAPPLICATIONS",
"/RESTARTAPPLICATIONS",
"/COMPONENTS=icons,ext,ext\\shellhere,ext\\guihere,gitlfs,assoc,assoc_sh,autoupdate",
}
if installDir != "" {
args = append(args, "/DIR="+installDir)
}
cmd := exec.Command(installerPath, args...)
hideWindowCmd(cmd)
err = cmd.Run()
if err != nil {
s.emitInstallProgress("error", 0, "安装失败: "+err.Error())
return fmt.Errorf("安装 Git 失败: %w", err)
}
// 3. 验证安装
time.Sleep(2 * time.Second) // 等待 PATH 更新
status := s.CheckGitInstalled()
if !status.Installed {
// 尝试在指定目录查找
gitExe := filepath.Join(installDir, "bin", "git.exe")
if _, err := os.Stat(gitExe); err == nil {
s.emitInstallProgress("done", 100, "Git 安装成功!路径: "+gitExe)
// 重新初始化 gitClient
s.reinitGitClient()
return nil
}
s.emitInstallProgress("error", 0, "安装完成但未检测到 Git请重启应用重试")
return fmt.Errorf("安装完成但未检测到 Git")
}
s.emitInstallProgress("done", 100, "Git 安装成功!版本: "+status.Version)
s.reinitGitClient()
return nil
}
// reinitGitClient 重新初始化 Git 客户端
func (s *AppService) reinitGitClient() {
s.gitClient = git.NewGitClient()
}
// emitInstallProgress 向前端发送安装进度事件
func (s *AppService) emitInstallProgress(phase string, percent float64, message string) {
if s.app != nil {
s.app.Event.Emit("git-install-progress", GitInstallProgress{
Phase: phase,
Percent: percent,
Message: message,
})
}
}
// downloadFile 下载文件并通过事件报告进度
func (s *AppService) downloadFile(url, dest string) error {
client := &http.Client{Timeout: 10 * time.Minute}
resp, err := client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
}
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
totalSize := resp.ContentLength
var downloaded int64
buf := make([]byte, 32*1024)
lastReport := time.Now()
for {
n, readErr := resp.Body.Read(buf)
if n > 0 {
_, writeErr := out.Write(buf[:n])
if writeErr != nil {
return writeErr
}
downloaded += int64(n)
// 每 300ms 报告一次进度
if time.Since(lastReport) > 300*time.Millisecond {
var pct float64
if totalSize > 0 {
pct = float64(downloaded) / float64(totalSize) * 100
}
sizeMB := float64(downloaded) / 1024 / 1024
msg := fmt.Sprintf("正在下载... %.1f MB", sizeMB)
if totalSize > 0 {
totalMB := float64(totalSize) / 1024 / 1024
msg = fmt.Sprintf("正在下载... %.1f / %.1f MB", sizeMB, totalMB)
}
s.emitInstallProgress("downloading", pct, msg)
lastReport = time.Now()
}
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}

View File

@@ -0,0 +1,8 @@
//go:build !windows
package app
import "os/exec"
// hideWindowCmd 非 Windows 平台无需处理
func hideWindowCmd(cmd *exec.Cmd) {}

View File

@@ -0,0 +1,14 @@
package app
import (
"os/exec"
"syscall"
)
// hideWindowCmd 在 Windows 上隐藏子进程的控制台窗口
func hideWindowCmd(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,10 +29,20 @@ func NewGitClient() *GitClient {
// Run 执行 git 命令,支持超时和代理设置
func (g *GitClient) Run(path string, args ...string) (string, error) {
return g.RunWithProxy(path, nil, args...)
}
// RunWithProxy 执行 git 命令useProxy 可覆盖全局代理设置
// useProxy == nil 时跟随 GitClient 自身的 Enabled 设置
func (g *GitClient) RunWithProxy(path string, useProxy *bool, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
defer cancel()
header := []string{"-C", path, "-c", "core.quotePath=false"}
if g.Enabled {
enableProxy := g.Enabled
if useProxy != nil {
enableProxy = *useProxy
}
if enableProxy {
header = append(header, "-c", "http.proxy="+g.Proxy, "-c", "https.proxy="+g.Proxy)
}
argsArr := append(header, args...)
@@ -60,18 +70,95 @@ func (g *GitClient) Push(path string) (string, error) {
return g.Run(path, "push")
}
// PushTo 推送到指定远程
func (g *GitClient) PushTo(path, remote, branch string) (string, error) {
return g.Run(path, "push", remote, branch)
}
// PullFrom 从指定远程拉取
func (g *GitClient) PullFrom(path, remote, branch string) (string, error) {
return g.Run(path, "pull", remote, branch)
}
// FetchRemote 拉取指定远程信息
func (g *GitClient) FetchRemote(path, remote string) (string, error) {
return g.Run(path, "fetch", remote)
}
func (g *GitClient) Status(path string) (string, error) {
return g.Run(path, "status")
}
func (g *GitClient) Clone(repoURL, path string) (string, error) {
return g.Run(".", "clone", repoURL, path)
// Clone 操作可能需要较长时间使用独立的超时设置10 分钟)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
args := []string{"-c", "core.quotePath=false"}
if g.Enabled {
args = append(args, "-c", "http.proxy="+g.Proxy, "-c", "https.proxy="+g.Proxy)
}
args = append(args, "clone", "--progress", repoURL, path)
log.Println(args)
cmd := exec.CommandContext(ctx, "git", args...)
hideWindow(cmd)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("git clone timeout")
}
if err != nil {
return "", fmt.Errorf("git clone error: %v, stderr: %s", err, stderr.String())
}
return stdout.String(), nil
}
func (g *GitClient) Fetch(path string) (string, error) {
return g.Run(path, "fetch", "--all")
}
// RemoteList 获取所有远程仓库列表 (git remote -v)
type RemoteInfo struct {
Name string
URL string
}
func (g *GitClient) RemoteList(path string) ([]RemoteInfo, error) {
out, err := g.Run(path, "remote", "-v")
if err != nil {
return nil, err
}
seen := make(map[string]bool)
var remotes []RemoteInfo
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
if line == "" {
continue
}
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
name := parts[0]
if seen[name] {
continue
}
seen[name] = true
remotes = append(remotes, RemoteInfo{Name: name, URL: parts[1]})
}
return remotes, nil
}
// AddRemote 添加远程仓库
func (g *GitClient) AddRemote(path, name, url string) (string, error) {
return g.Run(path, "remote", "add", name, url)
}
// RemoveRemote 删除远程仓库
func (g *GitClient) RemoveRemote(path, name string) (string, error) {
return g.Run(path, "remote", "remove", name)
}
func (g *GitClient) RemoteURL(path string) (string, error) {
out, err := g.Run(path, "config", "--get", "remote.origin.url")
if err != nil {
@@ -120,11 +207,16 @@ func (g *GitClient) Log(path string, count int) (string, error) {
)
}
// UnpushedCommits 获取当前分支上未推送到远程的提交哈希列表
// UnpushedCommits 获取当前分支上未推送到远程的提交哈希列表(默认 origin
func (g *GitClient) UnpushedCommits(path, branch string) (string, error) {
return g.Run(path, "rev-list", "origin/"+branch+"..HEAD")
}
// UnpushedCommitsTo 获取当前分支上未推送到指定远程的提交哈希列表
func (g *GitClient) UnpushedCommitsTo(path, remote, branch string) (string, error) {
return g.Run(path, "rev-list", remote+"/"+branch+"..HEAD")
}
// RevertCommit 撤回指定提交(创建一个反向提交)
func (g *GitClient) RevertCommit(path, hash string) (string, error) {
return g.Run(path, "revert", "--no-edit", hash)
@@ -146,16 +238,26 @@ func (g *GitClient) DeleteTag(path, name string) (string, error) {
return g.Run(path, "tag", "-d", name)
}
// PushTag 推送标签到远程
// PushTag 推送标签到远程(默认 origin
func (g *GitClient) PushTag(path, name string) (string, error) {
return g.Run(path, "push", "origin", name)
}
// DeleteRemoteTag 删除远程标签
// PushTagTo 推送标签到指定远程
func (g *GitClient) PushTagTo(path, remote, name string) (string, error) {
return g.Run(path, "push", remote, name)
}
// DeleteRemoteTag 删除远程标签(默认 origin
func (g *GitClient) DeleteRemoteTag(path, name string) (string, error) {
return g.Run(path, "push", "origin", "--delete", name)
}
// DeleteRemoteTagFrom 删除指定远程的标签
func (g *GitClient) DeleteRemoteTagFrom(path, remote, name string) (string, error) {
return g.Run(path, "push", remote, "--delete", name)
}
// BranchList 获取所有本地分支
func (g *GitClient) BranchList(path string) (string, error) {
return g.Run(path, "branch", "--format=%(refname:short)\t%(HEAD)")
@@ -327,6 +429,184 @@ func parseNameStatus(output string) []FileChange {
return changes
}
// CreateBranch 创建新分支
func (g *GitClient) CreateBranch(path, name string) (string, error) {
return g.Run(path, "branch", name)
}
// DeleteBranch 删除本地分支 (-d 安全删除)
func (g *GitClient) DeleteBranch(path, name string) (string, error) {
return g.Run(path, "branch", "-d", name)
}
// ForceDeleteBranch 强制删除本地分支 (-D)
func (g *GitClient) ForceDeleteBranch(path, name string) (string, error) {
return g.Run(path, "branch", "-D", name)
}
// MergeBranch 合并指定分支到当前分支
func (g *GitClient) MergeBranch(path, branch string) (string, error) {
return g.Run(path, "merge", branch)
}
// DeleteRemoteBranch 删除远程分支(默认 origin
func (g *GitClient) DeleteRemoteBranch(path, branch string) (string, error) {
return g.Run(path, "push", "origin", "--delete", branch)
}
// DeleteRemoteBranchFrom 删除指定远程的分支
func (g *GitClient) DeleteRemoteBranchFrom(path, remote, branch string) (string, error) {
return g.Run(path, "push", remote, "--delete", branch)
}
// RemoteBranchList 获取所有远程分支
func (g *GitClient) RemoteBranchList(path string) (string, error) {
return g.Run(path, "branch", "-r", "--format=%(refname:short)")
}
// RemoteBranchListByRemote 获取指定远程的分支
func (g *GitClient) RemoteBranchListByRemote(path, remote string) (string, error) {
return g.Run(path, "branch", "-r", "--list", remote+"/*", "--format=%(refname:short)")
}
// CheckoutNewBranch 从远程分支检出新本地分支
func (g *GitClient) CheckoutNewBranch(path, localBranch, remoteBranch string) (string, error) {
return g.Run(path, "checkout", "-b", localBranch, remoteBranch)
}
// StashSave 保存当前工作区变更到贮藏
func (g *GitClient) StashSave(path, message string) (string, error) {
if message != "" {
return g.Run(path, "stash", "push", "-m", message)
}
return g.Run(path, "stash", "push")
}
// StashList 获取贮藏列表
func (g *GitClient) StashList(path string) (string, error) {
return g.Run(path, "stash", "list", "--format=%gd\t%s\t%at")
}
// StashApply 应用贮藏(不删除)
func (g *GitClient) StashApply(path string, index int) (string, error) {
return g.Run(path, "stash", "apply", fmt.Sprintf("stash@{%d}", index))
}
// StashPop 应用贮藏并删除
func (g *GitClient) StashPop(path string, index int) (string, error) {
return g.Run(path, "stash", "pop", fmt.Sprintf("stash@{%d}", index))
}
// StashDrop 删除贮藏
func (g *GitClient) StashDrop(path string, index int) (string, error) {
return g.Run(path, "stash", "drop", fmt.Sprintf("stash@{%d}", index))
}
// GetGitGlobalConfig 获取 git 全局配置
func (g *GitClient) GetGitGlobalConfig(key string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "git", "config", "--global", "--get", key)
hideWindow(cmd)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", nil // key 不存在不算错误
}
return strings.TrimSpace(stdout.String()), nil
}
// SetGitGlobalConfig 设置 git 全局配置
func (g *GitClient) SetGitGlobalConfig(key, value string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "git", "config", "--global", key, value)
hideWindow(cmd)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("git config error: %v, stderr: %s", err, stderr.String())
}
return strings.TrimSpace(stdout.String()), nil
}
// --- 冲突处理 ---
// ConflictFiles 获取冲突文件列表 (git diff --name-only --diff-filter=U)
func (g *GitClient) ConflictFiles(path string) (string, error) {
return g.Run(path, "diff", "--name-only", "--diff-filter=U")
}
// GetConflictContent 获取冲突文件的完整内容(含冲突标记)
func (g *GitClient) GetConflictContent(path, filePath string) (string, error) {
return g.Run(path, "show", ":0:"+filePath)
}
// MarkResolved 将冲突文件标记为已解决 (git add <file>)
func (g *GitClient) MarkResolved(path string, files ...string) (string, error) {
args := append([]string{"add"}, files...)
return g.Run(path, args...)
}
// AbortMerge 中止合并 (git merge --abort)
func (g *GitClient) AbortMerge(path string) (string, error) {
return g.Run(path, "merge", "--abort")
}
// MergeStatus 检查是否处于合并状态
func (g *GitClient) MergeStatus(path string) bool {
// 检查 .git/MERGE_HEAD 文件是否存在
_, err := g.Run(path, "rev-parse", "--verify", "MERGE_HEAD")
return err == nil
}
// --- 提交搜索 ---
// SearchCommits 搜索提交历史 (git log --grep / --author / --after / --before)
func (g *GitClient) SearchCommits(path string, keyword, author string, maxCount int) (string, error) {
args := []string{"log"}
if maxCount > 0 {
args = append(args, fmt.Sprintf("--max-count=%d", maxCount))
}
if keyword != "" {
args = append(args, "--grep="+keyword, "-i")
}
if author != "" {
args = append(args, "--author="+author)
}
args = append(args, "--format=%H%n%h%n%an%n%ae%n%at%n%s%n---END---")
return g.Run(path, args...)
}
// --- 批量操作 ---
// QuickStatus 快速获取分支名和是否有变更(轻量级)
func (g *GitClient) QuickStatus(path string) (branch string, hasChanges bool, unpushed int, err error) {
branchOut, err := g.Branch(path)
if err != nil {
return "", false, 0, err
}
branch = strings.TrimSpace(branchOut)
// 检查是否有变更
statusOut, err := g.Run(path, "status", "--porcelain")
if err == nil {
hasChanges = strings.TrimSpace(statusOut) != ""
}
// 检查未推送提交数
unpushedOut, err := g.Run(path, "rev-list", "--count", "origin/"+branch+"..HEAD")
if err == nil {
fmt.Sscanf(strings.TrimSpace(unpushedOut), "%d", &unpushed)
}
return branch, hasChanges, unpushed, nil
}
// StatusText 返回变更状态的中文描述
func (fc FileChange) StatusText() string {
switch {

View File

@@ -4,8 +4,6 @@ import (
"fmt"
"os"
"strings"
"golang.org/x/sys/windows/registry"
)
// ProxyInfo 保存当前系统代理信息
@@ -25,8 +23,8 @@ type ProxyInfo struct {
}
// GetCurrentProxy 获取本机当前的代理设置
// 优先读取 Windows 系统代理(注册表),同时也读取环境变量中的代理配置
// 支持检测 Clash、V2Ray 等常见代理工具的配置
// 优先读取系统代理(Windows 注册表 / macOS scutil / Linux 环境变量),
// 同时也读取环境变量中的代理配置
func GetCurrentProxy() (*ProxyInfo, error) {
info := &ProxyInfo{}
@@ -34,9 +32,8 @@ func GetCurrentProxy() (*ProxyInfo, error) {
info.HTTPProxy = getEnvProxy("HTTP_PROXY", "http_proxy")
info.HTTPSProxy = getEnvProxy("HTTPS_PROXY", "https_proxy")
// 2. 从 Windows 注册表读取系统代理设置
if err := readWindowsSystemProxy(info); err != nil {
// 注册表读取失败不算致命错误,环境变量中可能仍有代理信息
// 2. 从系统设置读取代理(平台相关)
if err := readSystemProxy(info); err != nil {
fmt.Printf("读取系统代理设置时出错: %v\n", err)
}
@@ -54,42 +51,6 @@ func GetCurrentProxy() (*ProxyInfo, error) {
return info, nil
}
// readWindowsSystemProxy 从 Windows 注册表读取系统代理设置
func readWindowsSystemProxy(info *ProxyInfo) error {
key, err := registry.OpenKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
registry.QUERY_VALUE,
)
if err != nil {
return fmt.Errorf("无法打开注册表键: %w", err)
}
defer key.Close()
// 读取代理是否启用 (ProxyEnable: 0=关闭, 1=开启)
proxyEnable, _, err := key.GetIntegerValue("ProxyEnable")
if err != nil {
return fmt.Errorf("无法读取 ProxyEnable: %w", err)
}
info.Enabled = proxyEnable == 1
// 读取代理服务器地址
proxyServer, _, err := key.GetStringValue("ProxyServer")
if err == nil && proxyServer != "" {
info.Server = proxyServer
// 根据端口推断代理协议
info.Protocol = guessProtocol(proxyServer)
}
// 读取代理绕过列表
bypass, _, err := key.GetStringValue("ProxyOverride")
if err == nil {
info.Bypass = bypass
}
return nil
}
// getEnvProxy 依次检查多个环境变量名,返回第一个非空值
func getEnvProxy(names ...string) string {
for _, name := range names {
@@ -104,11 +65,9 @@ func getEnvProxy(names ...string) string {
// 例如 "http://127.0.0.1:7890" -> "127.0.0.1:7890"
func extractServerFromURL(proxyURL string) string {
s := proxyURL
// 去掉协议前缀
if idx := strings.Index(s, "://"); idx != -1 {
s = s[idx+3:]
}
// 去掉尾部斜杠
s = strings.TrimRight(s, "/")
return s
}
@@ -127,14 +86,14 @@ func extractProtocolFromURL(proxyURL string) string {
// V2Ray 默认: 10808(socks5), 10809(http)
func guessProtocol(server string) string {
knownPorts := map[string]string{
"7890": "http", // Clash HTTP
"7891": "socks5", // Clash SOCKS5
"7892": "http", // Clash mixed
"10808": "socks5", // V2Ray SOCKS5
"10809": "http", // V2Ray HTTP
"1080": "socks5", // 通用 SOCKS5
"1081": "http", // 通用 HTTP
"8080": "http", // 通用 HTTP
"7890": "http",
"7891": "socks5",
"7892": "http",
"10808": "socks5",
"10809": "http",
"1080": "socks5",
"1081": "http",
"8080": "http",
}
parts := strings.Split(server, ":")

82
internal/proxy_darwin.go Normal file
View File

@@ -0,0 +1,82 @@
//go:build darwin
package internal
import (
"fmt"
"os/exec"
"strings"
)
// readSystemProxy 从 macOS 系统偏好设置读取代理配置
// 使用 scutil --proxy 命令获取系统代理信息
func readSystemProxy(info *ProxyInfo) error {
out, err := exec.Command("scutil", "--proxy").Output()
if err != nil {
return fmt.Errorf("执行 scutil --proxy 失败: %w", err)
}
lines := strings.Split(string(out), "\n")
settings := make(map[string]string)
for _, line := range lines {
line = strings.TrimSpace(line)
parts := strings.SplitN(line, " : ", 2)
if len(parts) == 2 {
settings[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
// 检查 HTTP 代理
if settings["HTTPEnable"] == "1" {
host := settings["HTTPProxy"]
port := settings["HTTPPort"]
if host != "" {
info.Enabled = true
info.Server = host
if port != "" && port != "0" {
info.Server = host + ":" + port
}
info.Protocol = "http"
}
}
// 检查 HTTPS 代理
if !info.Enabled && settings["HTTPSEnable"] == "1" {
host := settings["HTTPSProxy"]
port := settings["HTTPSPort"]
if host != "" {
info.Enabled = true
info.Server = host
if port != "" && port != "0" {
info.Server = host + ":" + port
}
info.Protocol = "http"
}
}
// 检查 SOCKS 代理
if !info.Enabled && settings["SOCKSEnable"] == "1" {
host := settings["SOCKSProxy"]
port := settings["SOCKSPort"]
if host != "" {
info.Enabled = true
info.Server = host
if port != "" && port != "0" {
info.Server = host + ":" + port
}
info.Protocol = "socks5"
}
}
// 如果通过以上方式检测到了代理,根据端口再次猜测协议
if info.Enabled && info.Server != "" {
info.Protocol = guessProtocol(info.Server)
}
// 读取绕过列表
if exceptions, ok := settings["ExceptionsList"]; ok {
info.Bypass = exceptions
}
return nil
}

122
internal/proxy_linux.go Normal file
View File

@@ -0,0 +1,122 @@
//go:build linux
package internal
import (
"fmt"
"os/exec"
"strings"
)
// readSystemProxy 从 Linux 系统读取代理配置
// 优先尝试 GNOME (gsettings),失败则回退到环境变量
func readSystemProxy(info *ProxyInfo) error {
// 尝试通过 gsettings 读取 GNOME 系统代理
if err := readGnomeProxy(info); err == nil && info.Enabled {
return nil
}
// 尝试通过 KDE 配置读取代理
if err := readKDEProxy(info); err == nil && info.Enabled {
return nil
}
// Linux 上环境变量已由 GetCurrentProxy 中读取,此处无需额外操作
return nil
}
// readGnomeProxy 通过 gsettings 读取 GNOME 桌面环境的代理配置
func readGnomeProxy(info *ProxyInfo) error {
// 检查 gsettings 是否可用
if _, err := exec.LookPath("gsettings"); err != nil {
return fmt.Errorf("gsettings 不可用: %w", err)
}
// 读取代理模式
mode, err := gsettingsGet("org.gnome.system.proxy", "mode")
if err != nil {
return err
}
if mode != "'manual'" {
return nil // 代理未启用
}
// 读取 HTTP 代理
host, err := gsettingsGet("org.gnome.system.proxy.http", "host")
if err == nil && host != "" && host != "''" {
host = strings.Trim(host, "'")
port, _ := gsettingsGet("org.gnome.system.proxy.http", "port")
port = strings.TrimSpace(port)
if port != "" && port != "0" {
info.Enabled = true
info.Server = host + ":" + port
info.Protocol = guessProtocol(info.Server)
return nil
}
}
// 读取 SOCKS 代理
host, err = gsettingsGet("org.gnome.system.proxy.socks", "host")
if err == nil && host != "" && host != "''" {
host = strings.Trim(host, "'")
port, _ := gsettingsGet("org.gnome.system.proxy.socks", "port")
port = strings.TrimSpace(port)
if port != "" && port != "0" {
info.Enabled = true
info.Server = host + ":" + port
info.Protocol = "socks5"
return nil
}
}
return nil
}
// readKDEProxy 通过 kreadconfig5/kreadconfig6 读取 KDE 代理配置
func readKDEProxy(info *ProxyInfo) error {
// 尝试 kreadconfig5 或 kreadconfig6
var cmd string
if _, err := exec.LookPath("kreadconfig6"); err == nil {
cmd = "kreadconfig6"
} else if _, err := exec.LookPath("kreadconfig5"); err == nil {
cmd = "kreadconfig5"
} else {
return fmt.Errorf("kreadconfig 不可用")
}
// 读取代理类型
proxyType, err := kdeGet(cmd, "Proxy Settings", "ProxyType")
if err != nil || proxyType != "1" {
return nil // 0 = 无代理, 1 = 手动
}
// 读取 HTTP 代理
httpProxy, err := kdeGet(cmd, "Proxy Settings", "httpProxy")
if err == nil && httpProxy != "" {
info.Enabled = true
info.Server = extractServerFromURL(httpProxy)
info.Protocol = guessProtocol(info.Server)
return nil
}
return nil
}
// gsettingsGet 执行 gsettings get 命令
func gsettingsGet(schema, key string) (string, error) {
out, err := exec.Command("gsettings", "get", schema, key).Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
// kdeGet 执行 kreadconfig 命令读取 KDE 配置
func kdeGet(cmd, group, key string) (string, error) {
out, err := exec.Command(cmd, "--file", "kioslaverc", "--group", group, "--key", key).Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}

43
internal/proxy_windows.go Normal file
View File

@@ -0,0 +1,43 @@
package internal
import (
"fmt"
"golang.org/x/sys/windows/registry"
)
// readSystemProxy 从 Windows 注册表读取系统代理设置
func readSystemProxy(info *ProxyInfo) error {
key, err := registry.OpenKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
registry.QUERY_VALUE,
)
if err != nil {
return fmt.Errorf("无法打开注册表键: %w", err)
}
defer key.Close()
// 读取代理是否启用 (ProxyEnable: 0=关闭, 1=开启)
proxyEnable, _, err := key.GetIntegerValue("ProxyEnable")
if err != nil {
return fmt.Errorf("无法读取 ProxyEnable: %w", err)
}
info.Enabled = proxyEnable == 1
// 读取代理服务器地址
proxyServer, _, err := key.GetStringValue("ProxyServer")
if err == nil && proxyServer != "" {
info.Server = proxyServer
// 根据端口推断代理协议
info.Protocol = guessProtocol(proxyServer)
}
// 读取代理绕过列表
bypass, _, err := key.GetStringValue("ProxyOverride")
if err == nil {
info.Bypass = bypass
}
return nil
}