git-sync-tui 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -34
- package/README.zh-CN.md +126 -0
- package/dist/cli.js +576 -140
- package/package.json +16 -2
package/README.md
CHANGED
|
@@ -1,68 +1,126 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
<h1 align="center">🔄 git-sync-tui</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/git-sync-tui"><img src="https://img.shields.io/npm/v/git-sync-tui.svg?color=0ea5e9" alt="npm version"></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/git-sync-tui"><img src="https://img.shields.io/npm/dm/git-sync-tui.svg?color=10b981" alt="downloads"></a>
|
|
6
|
+
<a href="https://github.com/KiWi233333/git-sync-tui/actions/workflows/publish.yml"><img src="https://github.com/KiWi233333/git-sync-tui/actions/workflows/publish.yml/badge.svg" alt="publish"></a>
|
|
7
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/git-sync-tui.svg?color=8b5cf6" alt="node version"></a>
|
|
8
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-f59e0b.svg" alt="license"></a>
|
|
9
|
+
<a href="https://github.com/KiWi233333/git-sync-tui"><img src="https://img.shields.io/github/stars/KiWi233333/git-sync-tui?style=social" alt="GitHub Stars"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<b>Interactive TUI for cross-repository git commit synchronization</b>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
Cherry-pick commits from remote branches with an intuitive terminal UI.<br>
|
|
18
|
+
Select specific commits, preview changes, and sync with <code>--no-commit</code> mode for safe review.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="#-features">Features</a> ·
|
|
23
|
+
<a href="#-quick-start">Quick Start</a> ·
|
|
24
|
+
<a href="#-installation">Installation</a> ·
|
|
25
|
+
<a href="#-workflow">Workflow</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<a href="./README.md">English</a> | <a href="./README.zh-CN.md">中文</a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<img src="./assets/demo.gif" alt="git-sync-tui demo" width="680">
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
## ✨ Features
|
|
37
|
+
|
|
38
|
+
- 🎯 **Multi-select commits** — Cherry-pick non-consecutive commits with Space / Enter
|
|
39
|
+
- 🔍 **Branch search** — Fuzzy filter branches by keyword
|
|
40
|
+
- 👀 **Diff preview** — See `--stat` summary of selected commits before executing
|
|
41
|
+
- ⚡ **Safe mode** — `--no-commit` stages changes for review, never auto-commits
|
|
42
|
+
- ⚠️ **Conflict handling** — Clear display of conflicted files when cherry-pick fails
|
|
43
|
+
- 🌐 **Universal** — Works in any git repository, any language
|
|
44
|
+
|
|
45
|
+
## 🚀 Quick Start
|
|
17
46
|
|
|
18
47
|
```bash
|
|
48
|
+
# Install globally
|
|
19
49
|
npm install -g git-sync-tui
|
|
20
|
-
```
|
|
21
50
|
|
|
22
|
-
|
|
51
|
+
# Navigate to your git repo and run
|
|
52
|
+
cd your-project
|
|
53
|
+
git-sync-tui
|
|
54
|
+
```
|
|
23
55
|
|
|
24
|
-
##
|
|
56
|
+
## 📦 Installation
|
|
25
57
|
|
|
26
58
|
```bash
|
|
27
|
-
|
|
28
|
-
git-sync-tui
|
|
59
|
+
npm install -g git-sync-tui
|
|
29
60
|
```
|
|
30
61
|
|
|
31
|
-
|
|
62
|
+
> **Requirements:** Node.js >= 20
|
|
63
|
+
|
|
64
|
+
## 🔄 Workflow
|
|
32
65
|
|
|
33
66
|
```
|
|
34
|
-
|
|
67
|
+
Select Remote → Select Branch → Multi-select Commits → Preview Changes
|
|
68
|
+
↓
|
|
69
|
+
Review & Commit manually ← Cherry-pick --no-commit (staged, not committed)
|
|
35
70
|
```
|
|
36
71
|
|
|
37
|
-
|
|
72
|
+
## ⌨️ Keyboard Shortcuts
|
|
38
73
|
|
|
39
74
|
| Key | Action |
|
|
40
75
|
|-----|--------|
|
|
41
|
-
| `↑`
|
|
76
|
+
| `↑` `↓` | Navigate items |
|
|
42
77
|
| `Space` | Toggle commit selection |
|
|
43
78
|
| `Enter` | Confirm selection |
|
|
44
79
|
| `y` / `n` | Confirm / cancel execution |
|
|
80
|
+
| `/` | Search (in branch list) |
|
|
45
81
|
|
|
46
|
-
|
|
82
|
+
## 📋 After Sync
|
|
47
83
|
|
|
48
84
|
Changes are staged in your working tree (not committed). You can:
|
|
49
85
|
|
|
50
86
|
```bash
|
|
51
|
-
|
|
52
|
-
git
|
|
53
|
-
|
|
87
|
+
# Review staged changes
|
|
88
|
+
git diff --cached
|
|
89
|
+
|
|
90
|
+
# Commit when ready
|
|
91
|
+
git commit -m "sync: cherry-picked commits from feature-branch"
|
|
92
|
+
|
|
93
|
+
# Or discard all changes
|
|
94
|
+
git reset HEAD
|
|
54
95
|
```
|
|
55
96
|
|
|
56
|
-
##
|
|
97
|
+
## 💡 Use Cases
|
|
98
|
+
|
|
99
|
+
| Scenario | Description |
|
|
100
|
+
|----------|-------------|
|
|
101
|
+
| **Backport fixes** | Cherry-pick critical fixes from main to release branches |
|
|
102
|
+
| **Sync features** | Copy specific commits between feature branches |
|
|
103
|
+
| **Selective merge** | Pick individual commits instead of merging entire branches |
|
|
104
|
+
|
|
105
|
+
## 🛠️ Development
|
|
57
106
|
|
|
58
107
|
```bash
|
|
59
108
|
git clone https://github.com/KiWi233333/git-sync-tui.git
|
|
60
109
|
cd git-sync-tui
|
|
61
110
|
npm install
|
|
62
|
-
npm start
|
|
63
|
-
npm run build # Build with tsup
|
|
111
|
+
npm start
|
|
64
112
|
```
|
|
65
113
|
|
|
66
|
-
##
|
|
114
|
+
## 🏗️ Tech Stack
|
|
115
|
+
|
|
116
|
+
- [Ink](https://github.com/vadimdemedes/ink) — React for interactive CLI apps
|
|
117
|
+
- [@inkjs/ui](https://github.com/inkjs/ui) — UI components for Ink
|
|
118
|
+
- [simple-git](https://github.com/steveukx/git-js) — Git commands interface
|
|
119
|
+
|
|
120
|
+
## 🤝 Contributing
|
|
121
|
+
|
|
122
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
123
|
+
|
|
124
|
+
## 📄 License
|
|
67
125
|
|
|
68
|
-
MIT
|
|
126
|
+
[MIT](./LICENSE) © [KiWi233333](https://github.com/KiWi233333)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<h1 align="center">🔄 git-sync-tui</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/git-sync-tui"><img src="https://img.shields.io/npm/v/git-sync-tui.svg?color=0ea5e9" alt="npm version"></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/git-sync-tui"><img src="https://img.shields.io/npm/dm/git-sync-tui.svg?color=10b981" alt="downloads"></a>
|
|
6
|
+
<a href="https://github.com/KiWi233333/git-sync-tui/actions/workflows/publish.yml"><img src="https://github.com/KiWi233333/git-sync-tui/actions/workflows/publish.yml/badge.svg" alt="publish"></a>
|
|
7
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/git-sync-tui.svg?color=8b5cf6" alt="node version"></a>
|
|
8
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-f59e0b.svg" alt="license"></a>
|
|
9
|
+
<a href="https://github.com/KiWi233333/git-sync-tui"><img src="https://img.shields.io/github/stars/KiWi233333/git-sync-tui?style=social" alt="GitHub Stars"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<b>跨仓库 Git 提交同步的交互式 TUI 工具</b>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
通过直观的终端界面从远程分支 cherry-pick 提交。<br>
|
|
18
|
+
选择特定提交、预览变更,并使用 <code>--no-commit</code> 模式在提交前安全审查。
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="#-功能特性">功能</a> ·
|
|
23
|
+
<a href="#-快速开始">快速开始</a> ·
|
|
24
|
+
<a href="#-安装">安装</a> ·
|
|
25
|
+
<a href="#-工作流程">工作流程</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<a href="./README.md">English</a> | <a href="./README.zh-CN.md">中文</a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<!-- <p align="center">
|
|
33
|
+
<img src="./assets/demo.gif" alt="git-sync-tui 演示" width="680">
|
|
34
|
+
</p> -->
|
|
35
|
+
|
|
36
|
+
## ✨ 功能特性
|
|
37
|
+
|
|
38
|
+
- 🎯 **多选提交** — 使用 Space / Enter 选择不连续的提交进行 cherry-pick
|
|
39
|
+
- 🔍 **分支搜索** — 按关键词模糊过滤分支
|
|
40
|
+
- 👀 **差异预览** — 执行前查看所选提交的 `--stat` 摘要
|
|
41
|
+
- ⚡ **安全模式** — `--no-commit` 仅暂存变更供审查,不会自动提交
|
|
42
|
+
- ⚠️ **冲突处理** — cherry-pick 失败时清晰显示冲突文件
|
|
43
|
+
- 🌐 **通用性** — 适用于任何 Git 仓库,不限语言
|
|
44
|
+
|
|
45
|
+
## 🚀 快速开始
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 全局安装
|
|
49
|
+
npm install -g git-sync-tui
|
|
50
|
+
|
|
51
|
+
# 进入你的 Git 仓库并运行
|
|
52
|
+
cd your-project
|
|
53
|
+
git-sync-tui
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 📦 安装
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g git-sync-tui
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **环境要求:** Node.js >= 20
|
|
63
|
+
|
|
64
|
+
## 🔄 工作流程
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
选择远程仓库 → 选择分支 → 多选提交 → 预览变更
|
|
68
|
+
↓
|
|
69
|
+
手动审查并提交 ← Cherry-pick --no-commit(已暂存,未提交)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## ⌨️ 快捷键
|
|
73
|
+
|
|
74
|
+
| 按键 | 操作 |
|
|
75
|
+
|-----|------|
|
|
76
|
+
| `↑` `↓` | 上下导航 |
|
|
77
|
+
| `Space` | 切换提交选择 |
|
|
78
|
+
| `Enter` | 确认选择 |
|
|
79
|
+
| `y` / `n` | 确认 / 取消执行 |
|
|
80
|
+
| `/` | 搜索(在分支列表中) |
|
|
81
|
+
|
|
82
|
+
## 📋 同步后操作
|
|
83
|
+
|
|
84
|
+
变更已暂存在工作区(未提交)。你可以:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 查看暂存的变更
|
|
88
|
+
git diff --cached
|
|
89
|
+
|
|
90
|
+
# 准备好后提交
|
|
91
|
+
git commit -m "sync: 从 feature-branch cherry-pick 提交"
|
|
92
|
+
|
|
93
|
+
# 或放弃所有变更
|
|
94
|
+
git reset HEAD
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 💡 使用场景
|
|
98
|
+
|
|
99
|
+
| 场景 | 描述 |
|
|
100
|
+
|------|------|
|
|
101
|
+
| **回溯修复** | 从主分支 cherry-pick 关键修复到发布分支 |
|
|
102
|
+
| **同步特性** | 在特性分支间复制特定提交 |
|
|
103
|
+
| **选择性合并** | 选择单个提交而非合并整个分支 |
|
|
104
|
+
|
|
105
|
+
## 🛠️ 开发
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/KiWi233333/git-sync-tui.git
|
|
109
|
+
cd git-sync-tui
|
|
110
|
+
npm install
|
|
111
|
+
npm start
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🏗️ 技术栈
|
|
115
|
+
|
|
116
|
+
- [Ink](https://github.com/vadimdemedes/ink) — 用于构建交互式 CLI 应用的 React 框架
|
|
117
|
+
- [@inkjs/ui](https://github.com/inkjs/ui) — Ink 的 UI 组件库
|
|
118
|
+
- [simple-git](https://github.com/steveukx/git-js) — Git 命令接口
|
|
119
|
+
|
|
120
|
+
## 🤝 贡献
|
|
121
|
+
|
|
122
|
+
欢迎贡献代码!请随时提交 Pull Request。
|
|
123
|
+
|
|
124
|
+
## 📄 许可证
|
|
125
|
+
|
|
126
|
+
[MIT](./LICENSE) © [KiWi233333](https://github.com/KiWi233333)
|
package/dist/cli.js
CHANGED
|
@@ -5,12 +5,40 @@ import { render } from "ink";
|
|
|
5
5
|
import meow from "meow";
|
|
6
6
|
|
|
7
7
|
// src/app.tsx
|
|
8
|
-
import { useState as
|
|
9
|
-
import { Box as
|
|
8
|
+
import { useState as useState6, useEffect as useEffect3, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
9
|
+
import { Box as Box7, Text as Text7, useApp } from "ink";
|
|
10
|
+
import { Spinner as Spinner5 } from "@inkjs/ui";
|
|
11
|
+
|
|
12
|
+
// src/components/stash-prompt.tsx
|
|
13
|
+
import { Box, Text, useInput } from "ink";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
function StashPrompt({ onConfirm, onSkip }) {
|
|
16
|
+
useInput((input) => {
|
|
17
|
+
if (input === "y" || input === "Y") {
|
|
18
|
+
onConfirm();
|
|
19
|
+
} else if (input === "n" || input === "N") {
|
|
20
|
+
onSkip();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
|
|
24
|
+
/* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u68C0\u6D4B\u5230\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u7684\u53D8\u66F4" }),
|
|
26
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Cherry-pick \u64CD\u4F5C\u53EF\u80FD\u4F1A\u4E0E\u672A\u63D0\u4EA4\u7684\u5185\u5BB9\u51B2\u7A81" })
|
|
27
|
+
] }),
|
|
28
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "\u662F\u5426\u81EA\u52A8 stash \u4FDD\u5B58\u5F53\u524D\u53D8\u66F4? " }),
|
|
30
|
+
/* @__PURE__ */ jsx(Text, { color: "green", children: "[y]" }),
|
|
31
|
+
/* @__PURE__ */ jsx(Text, { children: " \u662F / " }),
|
|
32
|
+
/* @__PURE__ */ jsx(Text, { color: "red", children: "[n]" }),
|
|
33
|
+
/* @__PURE__ */ jsx(Text, { children: " \u5426\uFF0C\u7EE7\u7EED\u64CD\u4F5C" })
|
|
34
|
+
] })
|
|
35
|
+
] });
|
|
36
|
+
}
|
|
10
37
|
|
|
11
38
|
// src/components/remote-select.tsx
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
39
|
+
import { useState as useState2 } from "react";
|
|
40
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
41
|
+
import { Select, Spinner, TextInput } from "@inkjs/ui";
|
|
14
42
|
|
|
15
43
|
// src/hooks/use-git.ts
|
|
16
44
|
import { useState, useEffect, useCallback } from "react";
|
|
@@ -32,6 +60,10 @@ async function getRemotes() {
|
|
|
32
60
|
fetchUrl: r.refs.fetch
|
|
33
61
|
}));
|
|
34
62
|
}
|
|
63
|
+
async function addRemote(name, url) {
|
|
64
|
+
const git = getGit();
|
|
65
|
+
await git.addRemote(name, url);
|
|
66
|
+
}
|
|
35
67
|
async function getRemoteBranches(remote) {
|
|
36
68
|
const git = getGit();
|
|
37
69
|
try {
|
|
@@ -80,12 +112,29 @@ ${result.trim()}`);
|
|
|
80
112
|
return "(\u65E0\u6CD5\u83B7\u53D6 stat \u4FE1\u606F)";
|
|
81
113
|
}
|
|
82
114
|
}
|
|
83
|
-
async function
|
|
115
|
+
async function hasMergeCommits(hashes) {
|
|
116
|
+
const git = getGit();
|
|
117
|
+
try {
|
|
118
|
+
for (const hash of hashes) {
|
|
119
|
+
const result = await git.raw(["rev-list", "--merges", "-n", "1", hash]);
|
|
120
|
+
if (result.trim()) return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function cherryPick(hashes, useMainline = false) {
|
|
84
128
|
const git = getGit();
|
|
85
129
|
try {
|
|
86
130
|
const orderedHashes = [...hashes].reverse();
|
|
87
131
|
for (const hash of orderedHashes) {
|
|
88
|
-
|
|
132
|
+
const args = ["cherry-pick", "--no-commit"];
|
|
133
|
+
if (useMainline) {
|
|
134
|
+
args.push("-m", "1");
|
|
135
|
+
}
|
|
136
|
+
args.push(hash);
|
|
137
|
+
await git.raw(args);
|
|
89
138
|
}
|
|
90
139
|
return { success: true };
|
|
91
140
|
} catch (err) {
|
|
@@ -111,6 +160,29 @@ async function getStagedStat() {
|
|
|
111
160
|
return "";
|
|
112
161
|
}
|
|
113
162
|
}
|
|
163
|
+
async function isWorkingDirClean() {
|
|
164
|
+
const git = getGit();
|
|
165
|
+
const status = await git.status();
|
|
166
|
+
return status.isClean();
|
|
167
|
+
}
|
|
168
|
+
async function stash() {
|
|
169
|
+
const git = getGit();
|
|
170
|
+
try {
|
|
171
|
+
await git.stash(["push", "--include-untracked", "-m", "Auto-stash by git-sync-tui"]);
|
|
172
|
+
return true;
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function stashPop() {
|
|
178
|
+
const git = getGit();
|
|
179
|
+
try {
|
|
180
|
+
await git.stash(["pop"]);
|
|
181
|
+
return true;
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
114
186
|
|
|
115
187
|
// src/hooks/use-git.ts
|
|
116
188
|
function useAsync(fn, deps = []) {
|
|
@@ -169,259 +241,609 @@ function useCommitStat(hashes) {
|
|
|
169
241
|
}
|
|
170
242
|
|
|
171
243
|
// src/components/remote-select.tsx
|
|
172
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
244
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
173
245
|
function RemoteSelect({ onSelect }) {
|
|
174
|
-
const { data: remotes, loading, error } = useRemotes();
|
|
246
|
+
const { data: remotes, loading, error, reload } = useRemotes();
|
|
247
|
+
const [phase, setPhase] = useState2("list");
|
|
248
|
+
const [customUrl, setCustomUrl] = useState2("");
|
|
249
|
+
const [addError, setAddError] = useState2(null);
|
|
175
250
|
if (loading) {
|
|
176
|
-
return /* @__PURE__ */
|
|
251
|
+
return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsx2(Spinner, { label: "\u6B63\u5728\u83B7\u53D6\u8FDC\u7A0B\u4ED3\u5E93\u5217\u8868..." }) });
|
|
177
252
|
}
|
|
178
253
|
if (error) {
|
|
179
|
-
return /* @__PURE__ */
|
|
254
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
180
255
|
"\u83B7\u53D6\u8FDC\u7A0B\u4ED3\u5E93\u5931\u8D25: ",
|
|
181
256
|
error
|
|
182
257
|
] });
|
|
183
258
|
}
|
|
184
|
-
if (
|
|
185
|
-
return /* @__PURE__ */
|
|
259
|
+
if (phase === "adding") {
|
|
260
|
+
return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsx2(Spinner, { label: "\u6B63\u5728\u6DFB\u52A0\u8FDC\u7A0B\u4ED3\u5E93..." }) });
|
|
186
261
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
262
|
+
if (phase === "input-url") {
|
|
263
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
|
|
264
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "[1/5] \u6DFB\u52A0\u8FDC\u7A0B\u4ED3\u5E93" }),
|
|
265
|
+
addError && /* @__PURE__ */ jsx2(Text2, { color: "red", children: addError }),
|
|
266
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
267
|
+
/* @__PURE__ */ jsx2(Text2, { children: "\u4ED3\u5E93\u5730\u5740: " }),
|
|
268
|
+
/* @__PURE__ */ jsx2(
|
|
269
|
+
TextInput,
|
|
270
|
+
{
|
|
271
|
+
placeholder: "https://github.com/user/repo.git",
|
|
272
|
+
onSubmit: (url) => {
|
|
273
|
+
if (!url.trim()) {
|
|
274
|
+
setAddError("\u5730\u5740\u4E0D\u80FD\u4E3A\u7A7A");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
setCustomUrl(url.trim());
|
|
278
|
+
setPhase("input-name");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
] }),
|
|
283
|
+
/* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: "\u652F\u6301 HTTPS / SSH \u5730\u5740" })
|
|
284
|
+
] });
|
|
285
|
+
}
|
|
286
|
+
if (phase === "input-name") {
|
|
287
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
|
|
288
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "[1/5] \u6DFB\u52A0\u8FDC\u7A0B\u4ED3\u5E93" }),
|
|
289
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
290
|
+
"\u5730\u5740: ",
|
|
291
|
+
customUrl
|
|
292
|
+
] }),
|
|
293
|
+
addError && /* @__PURE__ */ jsx2(Text2, { color: "red", children: addError }),
|
|
294
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
295
|
+
/* @__PURE__ */ jsx2(Text2, { children: "\u8FDC\u7A0B\u540D\u79F0: " }),
|
|
296
|
+
/* @__PURE__ */ jsx2(
|
|
297
|
+
TextInput,
|
|
298
|
+
{
|
|
299
|
+
placeholder: "upstream",
|
|
300
|
+
onSubmit: async (name) => {
|
|
301
|
+
const remoteName = name.trim();
|
|
302
|
+
if (!remoteName) {
|
|
303
|
+
setAddError("\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (remotes?.some((r) => r.name === remoteName)) {
|
|
307
|
+
setAddError(`\u8FDC\u7A0B "${remoteName}" \u5DF2\u5B58\u5728`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
setAddError(null);
|
|
311
|
+
setPhase("adding");
|
|
312
|
+
try {
|
|
313
|
+
await addRemote(remoteName, customUrl);
|
|
314
|
+
reload();
|
|
315
|
+
onSelect(remoteName);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
setAddError(err.message);
|
|
318
|
+
setPhase("input-name");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
] })
|
|
324
|
+
] });
|
|
325
|
+
}
|
|
326
|
+
const options = [
|
|
327
|
+
...(remotes || []).map((r) => ({
|
|
328
|
+
label: `${r.name} ${r.fetchUrl}`,
|
|
329
|
+
value: r.name
|
|
330
|
+
})),
|
|
331
|
+
{
|
|
332
|
+
label: "+ \u6DFB\u52A0\u8FDC\u7A0B\u4ED3\u5E93...",
|
|
333
|
+
value: "__add_custom__"
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
|
|
337
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "[1/5] \u9009\u62E9\u8FDC\u7A0B\u4ED3\u5E93" }),
|
|
338
|
+
/* @__PURE__ */ jsx2(
|
|
339
|
+
Select,
|
|
340
|
+
{
|
|
341
|
+
options,
|
|
342
|
+
onChange: (value) => {
|
|
343
|
+
if (value === "__add_custom__") {
|
|
344
|
+
setPhase("input-url");
|
|
345
|
+
} else {
|
|
346
|
+
onSelect(value);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
)
|
|
194
351
|
] });
|
|
195
352
|
}
|
|
196
353
|
|
|
197
354
|
// src/components/branch-select.tsx
|
|
198
|
-
import { useState as
|
|
199
|
-
import { Box as
|
|
200
|
-
import { Select as Select2, Spinner as Spinner2, TextInput } from "@inkjs/ui";
|
|
201
|
-
import { jsx as
|
|
355
|
+
import { useState as useState3, useMemo } from "react";
|
|
356
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
357
|
+
import { Select as Select2, Spinner as Spinner2, TextInput as TextInput2 } from "@inkjs/ui";
|
|
358
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
202
359
|
function BranchSelect({ remote, onSelect }) {
|
|
203
360
|
const { data: branches, loading, error } = useBranches(remote);
|
|
204
|
-
const [filter, setFilter] =
|
|
361
|
+
const [filter, setFilter] = useState3("");
|
|
205
362
|
const filteredOptions = useMemo(() => {
|
|
206
363
|
if (!branches) return [];
|
|
207
364
|
const filtered = filter ? branches.filter((b) => b.toLowerCase().includes(filter.toLowerCase())) : branches;
|
|
208
365
|
return filtered.map((b) => ({ label: b, value: b }));
|
|
209
366
|
}, [branches, filter]);
|
|
210
367
|
if (loading) {
|
|
211
|
-
return /* @__PURE__ */
|
|
368
|
+
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Spinner2, { label: `\u6B63\u5728\u83B7\u53D6 ${remote} \u7684\u5206\u652F\u5217\u8868...` }) });
|
|
212
369
|
}
|
|
213
370
|
if (error) {
|
|
214
|
-
return /* @__PURE__ */
|
|
371
|
+
return /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
215
372
|
"\u83B7\u53D6\u5206\u652F\u5217\u8868\u5931\u8D25: ",
|
|
216
373
|
error
|
|
217
374
|
] });
|
|
218
375
|
}
|
|
219
376
|
if (!branches || branches.length === 0) {
|
|
220
|
-
return /* @__PURE__ */
|
|
377
|
+
return /* @__PURE__ */ jsx3(Text3, { color: "red", children: "\u672A\u627E\u5230\u8FDC\u7A0B\u5206\u652F" });
|
|
221
378
|
}
|
|
222
|
-
return /* @__PURE__ */
|
|
223
|
-
/* @__PURE__ */
|
|
379
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
|
|
380
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: "cyan", children: [
|
|
224
381
|
"[2/5] \u9009\u62E9\u5206\u652F (",
|
|
225
382
|
remote,
|
|
226
383
|
")"
|
|
227
384
|
] }),
|
|
228
|
-
/* @__PURE__ */
|
|
229
|
-
/* @__PURE__ */
|
|
230
|
-
/* @__PURE__ */
|
|
231
|
-
|
|
385
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
386
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\u641C\u7D22: " }),
|
|
387
|
+
/* @__PURE__ */ jsx3(
|
|
388
|
+
TextInput2,
|
|
232
389
|
{
|
|
233
390
|
placeholder: "\u8F93\u5165\u5173\u952E\u5B57\u8FC7\u6EE4\u5206\u652F...",
|
|
234
391
|
onChange: setFilter
|
|
235
392
|
}
|
|
236
393
|
)
|
|
237
394
|
] }),
|
|
238
|
-
/* @__PURE__ */
|
|
395
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "gray", dimColor: true, children: [
|
|
239
396
|
"\u5171 ",
|
|
240
397
|
branches.length,
|
|
241
398
|
" \u4E2A\u5206\u652F",
|
|
242
399
|
filter ? `\uFF0C\u5339\u914D ${filteredOptions.length} \u4E2A` : ""
|
|
243
400
|
] }),
|
|
244
|
-
filteredOptions.length > 0 ? /* @__PURE__ */
|
|
401
|
+
filteredOptions.length > 0 ? /* @__PURE__ */ jsx3(Select2, { options: filteredOptions, onChange: onSelect }) : /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u65E0\u5339\u914D\u5206\u652F" })
|
|
245
402
|
] });
|
|
246
403
|
}
|
|
247
404
|
|
|
248
405
|
// src/components/commit-list.tsx
|
|
249
|
-
import { useState as
|
|
250
|
-
import { Box as
|
|
251
|
-
import {
|
|
252
|
-
import { jsx as
|
|
406
|
+
import { useState as useState4, useMemo as useMemo2, useRef } from "react";
|
|
407
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
408
|
+
import { Spinner as Spinner3 } from "@inkjs/ui";
|
|
409
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
253
410
|
function CommitList({ remote, branch, onSelect }) {
|
|
254
411
|
const { data: commits, loading, error } = useCommits(remote, branch, 30);
|
|
255
|
-
const [
|
|
256
|
-
const
|
|
412
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
413
|
+
const [selectedHashes, setSelectedHashes] = useState4(/* @__PURE__ */ new Set());
|
|
414
|
+
const [shiftMode, setShiftMode] = useState4(false);
|
|
415
|
+
const anchorIndexRef = useRef(null);
|
|
416
|
+
const selectedKey = useMemo2(() => Array.from(selectedHashes).sort().join(","), [selectedHashes]);
|
|
417
|
+
const selectedArray = useMemo2(() => Array.from(selectedHashes), [selectedKey]);
|
|
418
|
+
const { stat, loading: statLoading } = useCommitStat(selectedArray);
|
|
419
|
+
const toggleCurrent = () => {
|
|
420
|
+
if (!commits || commits.length === 0) return;
|
|
421
|
+
const hash = commits[selectedIndex].hash;
|
|
422
|
+
setSelectedHashes((prev) => {
|
|
423
|
+
const next = new Set(prev);
|
|
424
|
+
if (next.has(hash)) {
|
|
425
|
+
next.delete(hash);
|
|
426
|
+
anchorIndexRef.current = null;
|
|
427
|
+
} else {
|
|
428
|
+
next.add(hash);
|
|
429
|
+
anchorIndexRef.current = selectedIndex;
|
|
430
|
+
}
|
|
431
|
+
return next;
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
const selectRange = (anchor, current) => {
|
|
435
|
+
if (!commits) return;
|
|
436
|
+
const start = Math.min(anchor, current);
|
|
437
|
+
const end = Math.max(anchor, current);
|
|
438
|
+
setSelectedHashes((prev) => {
|
|
439
|
+
const next = new Set(prev);
|
|
440
|
+
for (let i = start; i <= end; i++) {
|
|
441
|
+
next.add(commits[i].hash);
|
|
442
|
+
}
|
|
443
|
+
return next;
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
const toggleAll = () => {
|
|
447
|
+
if (!commits || commits.length === 0) return;
|
|
448
|
+
setSelectedHashes((prev) => {
|
|
449
|
+
if (prev.size === commits.length) {
|
|
450
|
+
anchorIndexRef.current = null;
|
|
451
|
+
return /* @__PURE__ */ new Set();
|
|
452
|
+
}
|
|
453
|
+
return new Set(commits.map((c) => c.hash));
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
const invertSelection = () => {
|
|
457
|
+
if (!commits || commits.length === 0) return;
|
|
458
|
+
setSelectedHashes((prev) => {
|
|
459
|
+
const next = /* @__PURE__ */ new Set();
|
|
460
|
+
for (const c of commits) {
|
|
461
|
+
if (!prev.has(c.hash)) {
|
|
462
|
+
next.add(c.hash);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return next;
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
const selectToCurrent = () => {
|
|
469
|
+
if (!commits || commits.length === 0) return;
|
|
470
|
+
setSelectedHashes((prev) => {
|
|
471
|
+
const next = new Set(prev);
|
|
472
|
+
for (let i = 0; i <= selectedIndex; i++) {
|
|
473
|
+
next.add(commits[i].hash);
|
|
474
|
+
}
|
|
475
|
+
return next;
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
useInput2((input, key) => {
|
|
479
|
+
if (!commits || commits.length === 0) return;
|
|
480
|
+
if (key.shift) {
|
|
481
|
+
if (!shiftMode) {
|
|
482
|
+
setShiftMode(true);
|
|
483
|
+
if (anchorIndexRef.current === null) {
|
|
484
|
+
anchorIndexRef.current = selectedIndex;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (key.upArrow) {
|
|
488
|
+
const newIndex = Math.max(0, selectedIndex - 1);
|
|
489
|
+
setSelectedIndex(newIndex);
|
|
490
|
+
selectRange(anchorIndexRef.current, newIndex);
|
|
491
|
+
} else if (key.downArrow) {
|
|
492
|
+
const newIndex = Math.min(commits.length - 1, selectedIndex + 1);
|
|
493
|
+
setSelectedIndex(newIndex);
|
|
494
|
+
selectRange(anchorIndexRef.current, newIndex);
|
|
495
|
+
} else if (input === " ") {
|
|
496
|
+
if (anchorIndexRef.current !== null) {
|
|
497
|
+
selectRange(anchorIndexRef.current, selectedIndex);
|
|
498
|
+
} else {
|
|
499
|
+
toggleCurrent();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (shiftMode) {
|
|
505
|
+
setShiftMode(false);
|
|
506
|
+
}
|
|
507
|
+
if (key.upArrow) {
|
|
508
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
509
|
+
} else if (key.downArrow) {
|
|
510
|
+
setSelectedIndex((prev) => Math.min(commits.length - 1, prev + 1));
|
|
511
|
+
} else if (input === " ") {
|
|
512
|
+
toggleCurrent();
|
|
513
|
+
} else if (input === "a" || input === "A") {
|
|
514
|
+
toggleAll();
|
|
515
|
+
} else if (input === "i" || input === "I") {
|
|
516
|
+
invertSelection();
|
|
517
|
+
} else if (input === "r" || input === "R") {
|
|
518
|
+
selectToCurrent();
|
|
519
|
+
} else if (key.return) {
|
|
520
|
+
if (selectedHashes.size > 0) {
|
|
521
|
+
onSelect(Array.from(selectedHashes), commits);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
257
525
|
if (loading) {
|
|
258
|
-
return /* @__PURE__ */
|
|
526
|
+
return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Spinner3, { label: `\u6B63\u5728\u83B7\u53D6 ${remote}/${branch} \u7684 commit \u5217\u8868...` }) });
|
|
259
527
|
}
|
|
260
528
|
if (error) {
|
|
261
|
-
return /* @__PURE__ */
|
|
529
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
262
530
|
"\u83B7\u53D6 commit \u5217\u8868\u5931\u8D25: ",
|
|
263
531
|
error
|
|
264
532
|
] });
|
|
265
533
|
}
|
|
266
534
|
if (!commits || commits.length === 0) {
|
|
267
|
-
return /* @__PURE__ */
|
|
535
|
+
return /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "\u8BE5\u5206\u652F\u6CA1\u6709 commit" });
|
|
268
536
|
}
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
/* @__PURE__ */
|
|
275
|
-
/* @__PURE__ */ jsxs3(Text3, { color: "gray", dimColor: true, children: [
|
|
537
|
+
const visibleCount = 10;
|
|
538
|
+
const startIdx = Math.max(0, Math.min(selectedIndex - Math.floor(visibleCount / 2), commits.length - visibleCount));
|
|
539
|
+
const visibleCommits = commits.slice(startIdx, startIdx + visibleCount);
|
|
540
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
|
|
541
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "[3/5] \u9009\u62E9\u8981\u540C\u6B65\u7684 commit" }),
|
|
542
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", dimColor: true, children: [
|
|
276
543
|
remote,
|
|
277
544
|
"/",
|
|
278
545
|
branch,
|
|
279
546
|
" \u6700\u8FD1 ",
|
|
280
547
|
commits.length,
|
|
281
|
-
" \u4E2A commit"
|
|
548
|
+
" \u4E2A commit | \u5DF2\u9009 ",
|
|
549
|
+
selectedHashes.size,
|
|
550
|
+
" \u4E2A",
|
|
551
|
+
shiftMode && /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: " | Shift \u6A21\u5F0F" })
|
|
282
552
|
] }),
|
|
283
|
-
/* @__PURE__ */
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
553
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
554
|
+
startIdx > 0 && /* @__PURE__ */ jsxs4(Text4, { color: "gray", dimColor: true, children: [
|
|
555
|
+
" \u2191 ",
|
|
556
|
+
startIdx,
|
|
557
|
+
" more..."
|
|
558
|
+
] }),
|
|
559
|
+
visibleCommits.map((c, i) => {
|
|
560
|
+
const actualIdx = startIdx + i;
|
|
561
|
+
const isSelected = selectedHashes.has(c.hash);
|
|
562
|
+
const isCursor = actualIdx === selectedIndex;
|
|
563
|
+
const isAnchor = actualIdx === anchorIndexRef.current;
|
|
564
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
565
|
+
/* @__PURE__ */ jsxs4(Text4, { backgroundColor: isCursor ? "blue" : void 0, color: isSelected ? "green" : "white", children: [
|
|
566
|
+
isCursor ? "\u25B6 " : " ",
|
|
567
|
+
isAnchor ? "\u2693 " : isSelected ? "\u25CF " : "\u25CB ",
|
|
568
|
+
c.shortHash,
|
|
569
|
+
" ",
|
|
570
|
+
c.message
|
|
571
|
+
] }),
|
|
572
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", dimColor: true, children: [
|
|
573
|
+
" (",
|
|
574
|
+
c.author,
|
|
575
|
+
")"
|
|
576
|
+
] })
|
|
577
|
+
] }, c.hash);
|
|
578
|
+
}),
|
|
579
|
+
startIdx + visibleCount < commits.length && /* @__PURE__ */ jsxs4(Text4, { color: "gray", dimColor: true, children: [
|
|
580
|
+
" \u2193 ",
|
|
581
|
+
commits.length - startIdx - visibleCount,
|
|
582
|
+
" more..."
|
|
583
|
+
] })
|
|
584
|
+
] }),
|
|
585
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 0, children: [
|
|
586
|
+
/* @__PURE__ */ jsxs4(Box4, { gap: 2, children: [
|
|
587
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
588
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u2191/\u2193" }),
|
|
589
|
+
" \u5BFC\u822A"
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
592
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "Space" }),
|
|
593
|
+
" \u9009\u62E9"
|
|
594
|
+
] }),
|
|
595
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
596
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "a" }),
|
|
597
|
+
" \u5168\u9009"
|
|
598
|
+
] }),
|
|
599
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
600
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "i" }),
|
|
601
|
+
" \u53CD\u9009"
|
|
602
|
+
] }),
|
|
603
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
604
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "Enter" }),
|
|
605
|
+
" \u786E\u8BA4"
|
|
606
|
+
] })
|
|
607
|
+
] }),
|
|
608
|
+
/* @__PURE__ */ jsxs4(Box4, { gap: 2, children: [
|
|
609
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
610
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "Shift+\u2191/\u2193" }),
|
|
611
|
+
" \u8FDE\u7EED\u9009\u62E9"
|
|
612
|
+
] }),
|
|
613
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
614
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "Shift+Space" }),
|
|
615
|
+
" \u8303\u56F4\u9009\u62E9"
|
|
616
|
+
] }),
|
|
617
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
618
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "r" }),
|
|
619
|
+
" \u9009\u81F3\u5F00\u5934"
|
|
620
|
+
] })
|
|
621
|
+
] })
|
|
622
|
+
] }),
|
|
623
|
+
selectedHashes.size > 0 && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
624
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "yellow", children: [
|
|
297
625
|
"\u5DF2\u9009 ",
|
|
298
|
-
selectedHashes.
|
|
626
|
+
selectedHashes.size,
|
|
299
627
|
" \u4E2A commit \u2014 diff --stat \u9884\u89C8:"
|
|
300
628
|
] }),
|
|
301
|
-
statLoading ? /* @__PURE__ */
|
|
629
|
+
statLoading ? /* @__PURE__ */ jsx4(Spinner3, { label: "\u52A0\u8F7D\u4E2D..." }) : /* @__PURE__ */ jsx4(Text4, { color: "gray", children: stat || "(\u65E0\u53D8\u66F4)" })
|
|
302
630
|
] })
|
|
303
631
|
] });
|
|
304
632
|
}
|
|
305
633
|
|
|
306
634
|
// src/components/confirm-panel.tsx
|
|
307
|
-
import { Box as
|
|
308
|
-
import { jsx as
|
|
309
|
-
function ConfirmPanel({ commits, selectedHashes, onConfirm, onCancel }) {
|
|
310
|
-
|
|
635
|
+
import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
|
|
636
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
637
|
+
function ConfirmPanel({ commits, selectedHashes, hasMerge, useMainline, onToggleMainline, onConfirm, onCancel }) {
|
|
638
|
+
useInput3((input) => {
|
|
311
639
|
if (input === "y" || input === "Y") {
|
|
312
640
|
onConfirm();
|
|
313
641
|
} else if (input === "n" || input === "N" || input === "q") {
|
|
314
642
|
onCancel();
|
|
643
|
+
} else if (hasMerge && (input === "m" || input === "M")) {
|
|
644
|
+
onToggleMainline();
|
|
315
645
|
}
|
|
316
646
|
});
|
|
317
647
|
const selectedCommits = selectedHashes.map((hash) => commits.find((c) => c.hash === hash)).filter(Boolean);
|
|
318
|
-
return /* @__PURE__ */
|
|
319
|
-
/* @__PURE__ */
|
|
320
|
-
/* @__PURE__ */
|
|
321
|
-
/* @__PURE__ */
|
|
648
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
|
|
649
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "[4/5] \u786E\u8BA4\u6267\u884C" }),
|
|
650
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
651
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
|
|
322
652
|
"\u5C06 cherry-pick --no-commit \u4EE5\u4E0B ",
|
|
323
653
|
selectedCommits.length,
|
|
324
654
|
" \u4E2A commit:"
|
|
325
655
|
] }),
|
|
326
|
-
selectedCommits.map((c) => /* @__PURE__ */
|
|
327
|
-
/* @__PURE__ */
|
|
656
|
+
selectedCommits.map((c) => /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
657
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
|
|
328
658
|
" ",
|
|
329
659
|
c.shortHash
|
|
330
660
|
] }),
|
|
331
|
-
/* @__PURE__ */
|
|
661
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
332
662
|
" ",
|
|
333
663
|
c.message
|
|
334
664
|
] }),
|
|
335
|
-
/* @__PURE__ */
|
|
665
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "gray", dimColor: true, children: [
|
|
336
666
|
" (",
|
|
337
667
|
c.author,
|
|
338
668
|
")"
|
|
339
669
|
] })
|
|
340
670
|
] }, c.hash))
|
|
341
671
|
] }),
|
|
342
|
-
/* @__PURE__ */
|
|
343
|
-
/* @__PURE__ */
|
|
344
|
-
/* @__PURE__ */
|
|
672
|
+
hasMerge && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "single", borderColor: "red", paddingX: 1, children: [
|
|
673
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "red", children: "\u68C0\u6D4B\u5230 Merge Commit" }),
|
|
674
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Cherry-pick \u5408\u5E76\u63D0\u4EA4\u9700\u8981\u6307\u5B9A\u7236\u8282\u70B9 (-m 1)" }),
|
|
675
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
676
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "[m]" }),
|
|
677
|
+
/* @__PURE__ */ jsx5(Text5, { children: " \u5207\u6362 -m 1: " }),
|
|
678
|
+
useMainline ? /* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u5DF2\u542F\u7528" }) : /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "\u672A\u542F\u7528" })
|
|
679
|
+
] })
|
|
345
680
|
] }),
|
|
346
|
-
/* @__PURE__ */
|
|
347
|
-
/* @__PURE__ */
|
|
348
|
-
/* @__PURE__ */
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
/* @__PURE__ */
|
|
681
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
682
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u26A0 " }),
|
|
683
|
+
/* @__PURE__ */ jsx5(Text5, { children: "\u4F7F\u7528 --no-commit \u6A21\u5F0F\uFF0C\u6539\u52A8\u5C06\u6682\u5B58\u5230\u5DE5\u4F5C\u533A\uFF0C\u9700\u624B\u52A8 commit" })
|
|
684
|
+
] }),
|
|
685
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
686
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "\u786E\u8BA4\u6267\u884C? " }),
|
|
687
|
+
/* @__PURE__ */ jsx5(Text5, { color: "green", children: "[y]" }),
|
|
688
|
+
/* @__PURE__ */ jsx5(Text5, { children: " \u786E\u8BA4 / " }),
|
|
689
|
+
/* @__PURE__ */ jsx5(Text5, { color: "red", children: "[n]" }),
|
|
690
|
+
/* @__PURE__ */ jsx5(Text5, { children: " \u53D6\u6D88" }),
|
|
691
|
+
hasMerge && /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
692
|
+
" / ",
|
|
693
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "[m]" }),
|
|
694
|
+
" \u5207\u6362 -m 1"
|
|
695
|
+
] })
|
|
352
696
|
] })
|
|
353
697
|
] });
|
|
354
698
|
}
|
|
355
699
|
|
|
356
700
|
// src/components/result-panel.tsx
|
|
357
|
-
import { useState as
|
|
358
|
-
import { Box as
|
|
701
|
+
import { useState as useState5, useEffect as useEffect2 } from "react";
|
|
702
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
359
703
|
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
360
|
-
import { jsx as
|
|
361
|
-
function ResultPanel({ selectedHashes, onDone }) {
|
|
362
|
-
const [phase, setPhase] =
|
|
363
|
-
const [result, setResult] =
|
|
364
|
-
const [stagedStat, setStagedStat] =
|
|
365
|
-
|
|
704
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
705
|
+
function ResultPanel({ selectedHashes, useMainline, stashed, onStashRestored, onDone }) {
|
|
706
|
+
const [phase, setPhase] = useState5("executing");
|
|
707
|
+
const [result, setResult] = useState5(null);
|
|
708
|
+
const [stagedStat, setStagedStat] = useState5("");
|
|
709
|
+
const [stashRestored, setStashRestored] = useState5(null);
|
|
710
|
+
const tryRestoreStash = async () => {
|
|
711
|
+
if (!stashed) return true;
|
|
712
|
+
setPhase("restoring");
|
|
713
|
+
const ok = await stashPop();
|
|
714
|
+
setStashRestored(ok);
|
|
715
|
+
if (ok) onStashRestored();
|
|
716
|
+
return ok;
|
|
717
|
+
};
|
|
718
|
+
useEffect2(() => {
|
|
366
719
|
async function run() {
|
|
367
|
-
const res = await cherryPick(selectedHashes);
|
|
720
|
+
const res = await cherryPick(selectedHashes, useMainline);
|
|
368
721
|
setResult(res);
|
|
369
722
|
if (res.success) {
|
|
370
723
|
const stat = await getStagedStat();
|
|
371
724
|
setStagedStat(stat);
|
|
725
|
+
await tryRestoreStash();
|
|
372
726
|
setPhase("done");
|
|
373
727
|
} else {
|
|
728
|
+
await tryRestoreStash();
|
|
374
729
|
setPhase("error");
|
|
375
730
|
}
|
|
376
731
|
}
|
|
377
732
|
run();
|
|
378
733
|
}, []);
|
|
379
734
|
if (phase === "executing") {
|
|
380
|
-
return /* @__PURE__ */
|
|
735
|
+
return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Spinner4, { label: `\u6B63\u5728\u6267\u884C cherry-pick --no-commit (${selectedHashes.length} \u4E2A commit)...` }) });
|
|
736
|
+
}
|
|
737
|
+
if (phase === "restoring") {
|
|
738
|
+
return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Spinner4, { label: "\u6B63\u5728\u6062\u590D\u5DE5\u4F5C\u533A (git stash pop)..." }) });
|
|
381
739
|
}
|
|
382
740
|
if (phase === "error" && result) {
|
|
383
|
-
return /* @__PURE__ */
|
|
384
|
-
/* @__PURE__ */
|
|
385
|
-
result.conflictFiles && result.conflictFiles.length > 0 && /* @__PURE__ */
|
|
386
|
-
/* @__PURE__ */
|
|
387
|
-
result.conflictFiles.map((f) => /* @__PURE__ */
|
|
741
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 1, children: [
|
|
742
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "red", children: "[5/5] Cherry-pick \u9047\u5230\u51B2\u7A81" }),
|
|
743
|
+
result.conflictFiles && result.conflictFiles.length > 0 && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "single", borderColor: "red", paddingX: 1, children: [
|
|
744
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "\u51B2\u7A81\u6587\u4EF6:" }),
|
|
745
|
+
result.conflictFiles.map((f) => /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
388
746
|
" ",
|
|
389
747
|
f
|
|
390
748
|
] }, f))
|
|
391
749
|
] }),
|
|
392
|
-
/* @__PURE__ */
|
|
393
|
-
/* @__PURE__ */
|
|
750
|
+
/* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u8BF7\u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add \u548C git commit" }),
|
|
751
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "\u6216\u6267\u884C git cherry-pick --abort \u653E\u5F03\u64CD\u4F5C" }),
|
|
752
|
+
stashed && stashRestored === false && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u6CE8\u610F: stash \u6062\u590D\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8 git stash pop" }),
|
|
753
|
+
stashed && stashRestored === true && /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)" })
|
|
394
754
|
] });
|
|
395
755
|
}
|
|
396
|
-
return /* @__PURE__ */
|
|
397
|
-
/* @__PURE__ */
|
|
398
|
-
/* @__PURE__ */
|
|
399
|
-
/* @__PURE__ */
|
|
400
|
-
/* @__PURE__ */
|
|
756
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 1, children: [
|
|
757
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "green", children: "[5/5] \u540C\u6B65\u5B8C\u6210!" }),
|
|
758
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [
|
|
759
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "\u6682\u5B58\u533A\u53D8\u66F4\u6982\u89C8 (git diff --cached --stat):" }),
|
|
760
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: stagedStat || "(\u65E0\u53D8\u66F4)" })
|
|
401
761
|
] }),
|
|
402
|
-
/* @__PURE__ */
|
|
403
|
-
/* @__PURE__ */
|
|
404
|
-
/* @__PURE__ */
|
|
405
|
-
/* @__PURE__ */
|
|
406
|
-
/* @__PURE__ */
|
|
762
|
+
stashed && (stashRestored ? /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)" }) : /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "stash pop \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8 git stash pop" })),
|
|
763
|
+
/* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u6539\u52A8\u5DF2\u6682\u5B58\u5230\u5DE5\u4F5C\u533A (--no-commit \u6A21\u5F0F)" }),
|
|
764
|
+
/* @__PURE__ */ jsx6(Text6, { children: "\u8BF7\u5BA1\u67E5\u540E\u624B\u52A8\u6267\u884C:" }),
|
|
765
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: " git diff --cached # \u67E5\u770B\u8BE6\u7EC6 diff" }),
|
|
766
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: ' git commit -m "\u540C\u6B65 commit" # \u63D0\u4EA4' }),
|
|
767
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: " git reset HEAD # \u6216\u653E\u5F03\u6240\u6709\u6539\u52A8" })
|
|
407
768
|
] });
|
|
408
769
|
}
|
|
409
770
|
|
|
410
771
|
// src/app.tsx
|
|
411
|
-
import {
|
|
772
|
+
import { execSync } from "child_process";
|
|
773
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
412
774
|
function App() {
|
|
413
|
-
const
|
|
414
|
-
const [
|
|
415
|
-
const [
|
|
416
|
-
const [
|
|
417
|
-
const [
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
775
|
+
const { exit } = useApp();
|
|
776
|
+
const [step, setStep] = useState6("checking");
|
|
777
|
+
const [remote, setRemote] = useState6("");
|
|
778
|
+
const [branch, setBranch] = useState6("");
|
|
779
|
+
const [selectedHashes, setSelectedHashes] = useState6([]);
|
|
780
|
+
const [commits, setCommits] = useState6([]);
|
|
781
|
+
const [hasMerge, setHasMerge] = useState6(false);
|
|
782
|
+
const [useMainline, setUseMainline] = useState6(false);
|
|
783
|
+
const [stashed, setStashed] = useState6(false);
|
|
784
|
+
const stashedRef = useRef2(false);
|
|
785
|
+
const stashRestoredRef = useRef2(false);
|
|
786
|
+
const restoreStashSync = useCallback2(() => {
|
|
787
|
+
if (stashedRef.current && !stashRestoredRef.current) {
|
|
788
|
+
try {
|
|
789
|
+
execSync("git stash pop", { stdio: "ignore" });
|
|
790
|
+
stashRestoredRef.current = true;
|
|
791
|
+
} catch {
|
|
792
|
+
try {
|
|
793
|
+
process.stderr.write("\n\u26A0 stash \u81EA\u52A8\u6062\u590D\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: git stash pop\n");
|
|
794
|
+
} catch {
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}, []);
|
|
799
|
+
const markStashRestored = useCallback2(() => {
|
|
800
|
+
stashRestoredRef.current = true;
|
|
801
|
+
}, []);
|
|
802
|
+
useEffect3(() => {
|
|
803
|
+
isWorkingDirClean().then((clean) => {
|
|
804
|
+
if (clean) {
|
|
805
|
+
setStep("remote");
|
|
806
|
+
} else {
|
|
807
|
+
setStep("stash-prompt");
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
const onSignal = () => {
|
|
811
|
+
restoreStashSync();
|
|
812
|
+
process.exit(0);
|
|
813
|
+
};
|
|
814
|
+
process.on("SIGINT", onSignal);
|
|
815
|
+
process.on("SIGTERM", onSignal);
|
|
816
|
+
process.on("beforeExit", restoreStashSync);
|
|
817
|
+
return () => {
|
|
818
|
+
process.off("SIGINT", onSignal);
|
|
819
|
+
process.off("SIGTERM", onSignal);
|
|
820
|
+
process.off("beforeExit", restoreStashSync);
|
|
821
|
+
};
|
|
822
|
+
}, [restoreStashSync]);
|
|
823
|
+
const doStash = async () => {
|
|
824
|
+
const ok = await stash();
|
|
825
|
+
if (ok) {
|
|
826
|
+
setStashed(true);
|
|
827
|
+
stashedRef.current = true;
|
|
828
|
+
}
|
|
829
|
+
setStep("remote");
|
|
830
|
+
};
|
|
831
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
832
|
+
/* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, children: [
|
|
833
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, inverse: true, color: "white", children: " git-sync-tui " }),
|
|
834
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
835
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: "\u4EA4\u4E92\u5F0F commit \u540C\u6B65\u5DE5\u5177 (cherry-pick --no-commit)" }),
|
|
836
|
+
stashed && /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: " (\u5DF2\u81EA\u52A8 stash)" })
|
|
423
837
|
] }),
|
|
424
|
-
step === "
|
|
838
|
+
step === "checking" && /* @__PURE__ */ jsx7(Spinner5, { label: "\u68C0\u67E5\u5DE5\u4F5C\u533A\u72B6\u6001..." }),
|
|
839
|
+
step === "stash-prompt" && /* @__PURE__ */ jsx7(
|
|
840
|
+
StashPrompt,
|
|
841
|
+
{
|
|
842
|
+
onConfirm: doStash,
|
|
843
|
+
onSkip: () => setStep("remote")
|
|
844
|
+
}
|
|
845
|
+
),
|
|
846
|
+
step === "remote" && /* @__PURE__ */ jsx7(
|
|
425
847
|
RemoteSelect,
|
|
426
848
|
{
|
|
427
849
|
onSelect: (r) => {
|
|
@@ -430,7 +852,7 @@ function App() {
|
|
|
430
852
|
}
|
|
431
853
|
}
|
|
432
854
|
),
|
|
433
|
-
step === "branch" && /* @__PURE__ */
|
|
855
|
+
step === "branch" && /* @__PURE__ */ jsx7(
|
|
434
856
|
BranchSelect,
|
|
435
857
|
{
|
|
436
858
|
remote,
|
|
@@ -440,39 +862,50 @@ function App() {
|
|
|
440
862
|
}
|
|
441
863
|
}
|
|
442
864
|
),
|
|
443
|
-
step === "commits" && /* @__PURE__ */
|
|
865
|
+
step === "commits" && /* @__PURE__ */ jsx7(
|
|
444
866
|
CommitList,
|
|
445
867
|
{
|
|
446
868
|
remote,
|
|
447
869
|
branch,
|
|
448
|
-
onSelect: (hashes, loadedCommits) => {
|
|
870
|
+
onSelect: async (hashes, loadedCommits) => {
|
|
449
871
|
setSelectedHashes(hashes);
|
|
450
872
|
setCommits(loadedCommits);
|
|
873
|
+
const merge = await hasMergeCommits(hashes);
|
|
874
|
+
setHasMerge(merge);
|
|
451
875
|
setStep("confirm");
|
|
452
876
|
}
|
|
453
877
|
}
|
|
454
878
|
),
|
|
455
|
-
step === "confirm" && /* @__PURE__ */
|
|
879
|
+
step === "confirm" && /* @__PURE__ */ jsx7(
|
|
456
880
|
ConfirmPanel,
|
|
457
881
|
{
|
|
458
882
|
commits,
|
|
459
883
|
selectedHashes,
|
|
884
|
+
hasMerge,
|
|
885
|
+
useMainline,
|
|
886
|
+
onToggleMainline: () => setUseMainline((v) => !v),
|
|
460
887
|
onConfirm: () => setStep("result"),
|
|
461
888
|
onCancel: () => setStep("commits")
|
|
462
889
|
}
|
|
463
890
|
),
|
|
464
|
-
step === "result" && /* @__PURE__ */
|
|
891
|
+
step === "result" && /* @__PURE__ */ jsx7(
|
|
465
892
|
ResultPanel,
|
|
466
893
|
{
|
|
467
894
|
selectedHashes,
|
|
468
|
-
|
|
895
|
+
useMainline,
|
|
896
|
+
stashed,
|
|
897
|
+
onStashRestored: markStashRestored,
|
|
898
|
+
onDone: () => {
|
|
899
|
+
restoreStashSync();
|
|
900
|
+
exit();
|
|
901
|
+
}
|
|
469
902
|
}
|
|
470
903
|
)
|
|
471
904
|
] });
|
|
472
905
|
}
|
|
473
906
|
|
|
474
907
|
// src/cli.tsx
|
|
475
|
-
import { jsx as
|
|
908
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
476
909
|
var cli = meow(
|
|
477
910
|
`
|
|
478
911
|
\u7528\u6CD5
|
|
@@ -487,13 +920,16 @@ var cli = meow(
|
|
|
487
920
|
\u4F7F\u7528 cherry-pick --no-commit \u6A21\u5F0F\uFF0C\u540C\u6B65\u540E\u53EF\u5BA1\u67E5\u518D\u63D0\u4EA4\u3002
|
|
488
921
|
|
|
489
922
|
\u5FEB\u6377\u952E
|
|
490
|
-
Space
|
|
491
|
-
|
|
492
|
-
\
|
|
493
|
-
|
|
923
|
+
Space \u9009\u62E9/\u53D6\u6D88 commit
|
|
924
|
+
Shift+\u2191/\u2193 \u8FDE\u7EED\u9009\u62E9
|
|
925
|
+
a \u5168\u9009/\u53D6\u6D88\u5168\u9009
|
|
926
|
+
i \u53CD\u9009
|
|
927
|
+
r \u9009\u81F3\u5F00\u5934
|
|
928
|
+
Enter \u786E\u8BA4\u9009\u62E9
|
|
929
|
+
y/n \u786E\u8BA4/\u53D6\u6D88\u6267\u884C
|
|
494
930
|
`,
|
|
495
931
|
{
|
|
496
932
|
importMeta: import.meta
|
|
497
933
|
}
|
|
498
934
|
);
|
|
499
|
-
render(/* @__PURE__ */
|
|
935
|
+
render(/* @__PURE__ */ jsx8(App, {}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-sync-tui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
|
+
"packageManager": "pnpm@10.32.1",
|
|
5
6
|
"description": "Interactive TUI tool for cross-repo git commit synchronization (cherry-pick --no-commit)",
|
|
6
7
|
"author": "KiWi233333",
|
|
7
8
|
"repository": {
|
|
@@ -12,6 +13,10 @@
|
|
|
12
13
|
"bugs": {
|
|
13
14
|
"url": "https://github.com/KiWi233333/git-sync-tui/issues"
|
|
14
15
|
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"registry": "https://registry.npmjs.org",
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
15
20
|
"bin": {
|
|
16
21
|
"git-sync-tui": "dist/cli.js"
|
|
17
22
|
},
|
|
@@ -21,11 +26,20 @@
|
|
|
21
26
|
"engines": {
|
|
22
27
|
"node": ">=20"
|
|
23
28
|
},
|
|
29
|
+
"volta": {
|
|
30
|
+
"node": "24.14.0"
|
|
31
|
+
},
|
|
24
32
|
"scripts": {
|
|
25
33
|
"start": "tsx src/cli.tsx",
|
|
26
34
|
"dev": "tsx watch src/cli.tsx",
|
|
27
35
|
"build": "tsup",
|
|
28
|
-
"prepublishOnly": "
|
|
36
|
+
"prepublishOnly": "pnpm run build",
|
|
37
|
+
"release": "pnpm run release:patch",
|
|
38
|
+
"release:patch": "pnpm version patch && git push --follow-tags",
|
|
39
|
+
"release:minor": "pnpm version minor && git push --follow-tags",
|
|
40
|
+
"release:major": "pnpm version major && git push --follow-tags",
|
|
41
|
+
"release:beta": "pnpm version prerelease --preid=beta && git push --follow-tags",
|
|
42
|
+
"release:retag": "tsx scripts/retag.ts"
|
|
29
43
|
},
|
|
30
44
|
"keywords": [
|
|
31
45
|
"git",
|