diff --git a/.task/checksum/generate-bindings--BUILD_FLAGS--buildvcs-false--gcflags-all---l-- b/.task/checksum/generate-bindings--BUILD_FLAGS--buildvcs-false--gcflags-all---l-- index 6b88588..85d7703 100644 --- a/.task/checksum/generate-bindings--BUILD_FLAGS--buildvcs-false--gcflags-all---l-- +++ b/.task/checksum/generate-bindings--BUILD_FLAGS--buildvcs-false--gcflags-all---l-- @@ -1 +1 @@ -c6a7af2915380768678ecf3032701af1 +b3473eff2f1694108cc3ae68b4ae04f0 diff --git a/bin/gitpilot.exe b/bin/gitpilot.exe index 7750361..310787c 100644 Binary files a/bin/gitpilot.exe and b/bin/gitpilot.exe differ diff --git a/frontend/app/components/ContentArea.vue b/frontend/app/components/ContentArea.vue index 74cfdfd..91586e4 100644 --- a/frontend/app/components/ContentArea.vue +++ b/frontend/app/components/ContentArea.vue @@ -19,6 +19,7 @@ import { UserOutlined, RollbackOutlined, ExclamationCircleOutlined, + CloseCircleOutlined, } from '@ant-design/icons-vue' import { Modal } from 'ant-design-vue' import { AppService } from '../../bindings/github.com/zhuy1228/GitPilot/internal/app' @@ -614,6 +615,33 @@ async function resetToCommit(commit, mode = 'hard') { }) } +async function revertCommit(commit) { + if (!props.project?.path || !commit) return + Modal.confirm({ + title: '确认撤回提交', + icon: h(ExclamationCircleOutlined), + content: h('div', [ + h('p', '将创建一个新提交来撤回以下提交的更改:'), + h('p', { style: 'font-family: monospace; color: #89b4fa;' }, `${commit.shortHash} - ${commit.message}`), + h('p', { style: 'color: var(--text-muted); font-size: 12px; margin-top: 8px;' }, '此操作会生成一个新的反向提交,不会丢失历史记录。'), + ]), + okText: '确认撤回', + cancelText: '取消', + okButtonProps: { danger: true }, + async onOk() { + try { + await AppService.RevertCommit(props.project.path, commit.hash) + await loadStatus() + selectedCommit.value = null + commitDiff.value = '' + } catch (e) { + console.error('撤回提交失败:', e) + Modal.error({ title: '撤回失败', content: String(e) }) + } + }, + }) +} + function formatTime(ts) { if (!ts) return '' const d = new Date(ts * 1000) @@ -842,41 +870,56 @@ function formatTime(ts) { v-for="log in commitLogs" :key="log.hash" class="commit-item" - :class="{ active: selectedCommit?.hash === log.hash }" + :class="{ active: selectedCommit?.hash === log.hash, unpushed: !log.pushed }" @click="selectCommit(log)" >
+
{{ log.message }}
- +
+ - + - - + + + + + + + +
{{ log.shortHash }} + 未推送 {{ log.author }} {{ formatTime(log.timestamp) }}
@@ -1379,22 +1422,52 @@ function formatTime(ts) { min-width: 0; } -.commit-reset-btn { - visibility: hidden; - color: var(--text-muted) !important; +.commit-action-btns { + display: flex; + align-items: center; + gap: 0; flex-shrink: 0; + visibility: hidden; +} + +.commit-action-btn { + color: var(--text-muted) !important; width: 24px; height: 24px; } -.commit-reset-btn:hover { +.commit-action-btn:hover { color: #f38ba8 !important; } -.commit-item:hover .commit-reset-btn { +.commit-item:hover .commit-action-btns { visibility: visible; } +/* 未推送标识 */ +.commit-unpushed-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #a6e3a1; + flex-shrink: 0; + margin-right: 2px; +} + +.commit-push-tag { + font-size: 10px; + padding: 0 5px; + border-radius: 3px; + background: rgba(166, 227, 161, 0.15); + color: #a6e3a1; + line-height: 16px; + flex-shrink: 0; +} + +.commit-item.unpushed { + border-left: 2px solid #a6e3a1; +} + .commit-msg { font-size: 13px; color: var(--text-primary); diff --git a/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/appservice.js b/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/appservice.js index b0e96e2..95bd4c5 100644 --- a/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/appservice.js +++ b/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/appservice.js @@ -282,6 +282,16 @@ export function ResetProject(path, hash, mode) { return $Call.ByID(2061148684, path, hash, mode); } +/** + * RevertCommit 撤回指定提交(生成一个反向提交) + * @param {string} path + * @param {string} hash + * @returns {$CancellablePromise} + */ +export function RevertCommit(path, hash) { + return $Call.ByID(1334788539, path, hash); +} + /** * SelectDirectory 打开系统文件夹选择器,返回选中的路径 * @returns {$CancellablePromise} diff --git a/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/models.js b/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/models.js index 7370398..a8fc047 100644 --- a/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/models.js +++ b/frontend/bindings/github.com/zhuy1228/GitPilot/internal/app/models.js @@ -133,6 +133,13 @@ export class CommitLog { */ this["message"] = ""; } + if (!("pushed" in $$source)) { + /** + * @member + * @type {boolean} + */ + this["pushed"] = false; + } Object.assign(this, $$source); } diff --git a/internal/app/service.go b/internal/app/service.go index ac1e929..c7c17a0 100644 --- a/internal/app/service.go +++ b/internal/app/service.go @@ -569,6 +569,7 @@ type CommitLog struct { Email string `json:"email"` Timestamp int64 `json:"timestamp"` Message string `json:"message"` + Pushed bool `json:"pushed"` } // BranchInfo 分支信息 @@ -589,7 +590,39 @@ func (s *AppService) GetCommitLog(path string, count int) ([]CommitLog, error) { if err != nil { return nil, fmt.Errorf("获取提交历史失败: %w", err) } - return parseCommitLog(out), nil + logs := parseCommitLog(out) + + // 获取当前分支名,查询未推送的提交 + branch, brErr := s.gitClient.Branch(path) + if brErr == nil { + branch = strings.TrimSpace(branch) + unpushedOut, upErr := s.gitClient.UnpushedCommits(path, branch) + unpushedSet := make(map[string]bool) + if upErr == nil { + for _, h := range strings.Split(strings.TrimSpace(unpushedOut), "\n") { + if h != "" { + unpushedSet[h] = true + } + } + } + for i := range logs { + logs[i].Pushed = !unpushedSet[logs[i].Hash] + } + } + + return logs, nil +} + +// RevertCommit 撤回指定提交(生成一个反向提交) +func (s *AppService) RevertCommit(path, hash string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return fmt.Errorf("项目路径不存在: %s", path) + } + if strings.TrimSpace(hash) == "" { + return fmt.Errorf("提交哈希不能为空") + } + _, err := s.gitClient.RevertCommit(path, strings.TrimSpace(hash)) + return err } func parseCommitLog(output string) []CommitLog { diff --git a/internal/git/client.go b/internal/git/client.go index cceb773..a47a9d2 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -119,6 +119,16 @@ func (g *GitClient) Log(path string, count int) (string, error) { ) } +// UnpushedCommits 获取当前分支上未推送到远程的提交哈希列表 +func (g *GitClient) UnpushedCommits(path, branch string) (string, error) { + return g.Run(path, "rev-list", "origin/"+branch+"..HEAD") +} + +// RevertCommit 撤回指定提交(创建一个反向提交) +func (g *GitClient) RevertCommit(path, hash string) (string, error) { + return g.Run(path, "revert", "--no-edit", hash) +} + // BranchList 获取所有本地分支 func (g *GitClient) BranchList(path string) (string, error) { return g.Run(path, "branch", "--format=%(refname:short)\t%(HEAD)")