git-sync-tui 0.1.5 → 0.1.7

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/dist/cli.js CHANGED
@@ -11,9 +11,9 @@ import { render } from "ink";
11
11
  import meow from "meow";
12
12
 
13
13
  // src/app.tsx
14
- import { useState as useState7, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback2 } from "react";
15
- import { Box as Box9, useApp } from "ink";
16
- import { Spinner as Spinner6 } from "@inkjs/ui";
14
+ import { useState as useState9, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback3 } from "react";
15
+ import { Box as Box11, useApp } from "ink";
16
+ import { Spinner as Spinner7 } from "@inkjs/ui";
17
17
 
18
18
  // src/components/ui.tsx
19
19
  import React from "react";
@@ -92,11 +92,14 @@ function StatusPanel({ type, title, children }) {
92
92
  children
93
93
  ] });
94
94
  }
95
- function AppHeader({ step, stashed }) {
95
+ function AppHeader({ step, stashed, noCommit }) {
96
96
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
97
97
  /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
98
98
  /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "white", bold: true, children: " git-sync-tui " }),
99
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "cherry-pick --no-commit" }),
99
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
100
+ "cherry-pick",
101
+ noCommit ? " --no-commit" : ""
102
+ ] }),
100
103
  stashed && /* @__PURE__ */ jsx(Text, { backgroundColor: "yellow", color: "white", bold: true, children: " STASHED " })
101
104
  ] }),
102
105
  /* @__PURE__ */ jsxs(Box, { marginTop: 0, children: [
@@ -187,6 +190,8 @@ async function getCommits(remote2, branch2, count2 = 100) {
187
190
  const result = await git.raw([
188
191
  "log",
189
192
  ref,
193
+ "--first-parent",
194
+ "--no-merges",
190
195
  `--max-count=${count2}`,
191
196
  "--format=%H%n%h%n%s%n%an%n%ar%n---"
192
197
  ]);
@@ -281,6 +286,45 @@ async function getStagedStat() {
281
286
  return "";
282
287
  }
283
288
  }
289
+ var BACKUP_BRANCH_PREFIX = "git-sync-backup";
290
+ async function createBackupBranch() {
291
+ const git = getGit();
292
+ const timestamp = Date.now();
293
+ const branchName = `${BACKUP_BRANCH_PREFIX}-${timestamp}`;
294
+ await git.raw(["branch", branchName]);
295
+ return branchName;
296
+ }
297
+ async function restoreFromBackup(backupBranch) {
298
+ const git = getGit();
299
+ try {
300
+ await git.raw(["rev-parse", "--verify", backupBranch]);
301
+ await git.raw(["reset", "--hard", backupBranch]);
302
+ await git.raw(["branch", "-D", backupBranch]);
303
+ return { success: true };
304
+ } catch (err) {
305
+ return {
306
+ success: false,
307
+ error: `\u56DE\u9000\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: git reset --hard ${backupBranch}
308
+ ${err.message}`
309
+ };
310
+ }
311
+ }
312
+ async function deleteBackupBranch(backupBranch) {
313
+ const git = getGit();
314
+ try {
315
+ await git.raw(["branch", "-D", backupBranch]);
316
+ } catch {
317
+ }
318
+ }
319
+ async function getCurrentBranch() {
320
+ const git = getGit();
321
+ const result = await git.raw(["rev-parse", "--abbrev-ref", "HEAD"]);
322
+ return result.trim();
323
+ }
324
+ async function createBranchFrom(newBranch, baseBranch) {
325
+ const git = getGit();
326
+ await git.raw(["checkout", "-b", newBranch, baseBranch]);
327
+ }
284
328
  async function isWorkingDirClean() {
285
329
  const git = getGit();
286
330
  const status = await git.status();
@@ -304,6 +348,87 @@ async function stashPop() {
304
348
  return false;
305
349
  }
306
350
  }
351
+ async function abortCherryPick() {
352
+ const git = getGit();
353
+ try {
354
+ await git.raw(["cherry-pick", "--abort"]);
355
+ } catch {
356
+ }
357
+ }
358
+ async function getConflictFiles() {
359
+ const git = getGit();
360
+ const status = await git.status();
361
+ return status.conflicted;
362
+ }
363
+ async function continueCherryPick() {
364
+ const git = getGit();
365
+ try {
366
+ await git.raw(["cherry-pick", "--continue", "--no-edit"]);
367
+ return { success: true };
368
+ } catch (err) {
369
+ const msg = err.message || "";
370
+ if (msg.includes("nothing to commit") || msg.includes("empty") || msg.includes("allow-empty")) {
371
+ return { success: false, error: msg, empty: true };
372
+ }
373
+ try {
374
+ const status = await git.status();
375
+ const conflictFiles = status.conflicted;
376
+ return {
377
+ success: false,
378
+ error: msg,
379
+ conflictFiles: conflictFiles.length > 0 ? conflictFiles : void 0
380
+ };
381
+ } catch {
382
+ return { success: false, error: msg };
383
+ }
384
+ }
385
+ }
386
+ async function clearCherryPickState() {
387
+ try {
388
+ const gitDir = await getGitDir();
389
+ const cpHead = join(gitDir, "CHERRY_PICK_HEAD");
390
+ if (existsSync(cpHead)) {
391
+ unlinkSync(cpHead);
392
+ }
393
+ } catch {
394
+ }
395
+ }
396
+ async function cherryPickOne(hash, useMainline = false, noCommit = true) {
397
+ const git = getGit();
398
+ try {
399
+ const args = ["cherry-pick"];
400
+ if (noCommit) args.push("--no-commit");
401
+ if (useMainline) args.push("-m", "1");
402
+ args.push(hash);
403
+ await git.raw(args);
404
+ return { success: true };
405
+ } catch (err) {
406
+ try {
407
+ const status = await git.status();
408
+ const conflictFiles = status.conflicted;
409
+ return {
410
+ success: false,
411
+ error: err.message,
412
+ conflictFiles: conflictFiles.length > 0 ? conflictFiles : void 0
413
+ };
414
+ } catch {
415
+ return { success: false, error: err.message };
416
+ }
417
+ }
418
+ }
419
+ async function skipCherryPick() {
420
+ const git = getGit();
421
+ await git.raw(["cherry-pick", "--skip"]);
422
+ }
423
+ async function isCherryPickInProgress() {
424
+ const git = getGit();
425
+ try {
426
+ const gitDir = await getGitDir();
427
+ return existsSync(join(gitDir, "CHERRY_PICK_HEAD"));
428
+ } catch {
429
+ return false;
430
+ }
431
+ }
307
432
  var STASH_GUARD_FILE = "git-sync-tui-stash-guard";
308
433
  async function getGitDir() {
309
434
  const git = getGit();
@@ -1043,11 +1168,107 @@ function CommitList({ remote: remote2, branch: branch2, onSelect, onBack }) {
1043
1168
  ] });
1044
1169
  }
1045
1170
 
1046
- // src/components/confirm-panel.tsx
1171
+ // src/components/branch-check.tsx
1172
+ import { useState as useState6, useEffect as useEffect4, useRef as useRef3 } from "react";
1047
1173
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
1174
+ import { Spinner as Spinner5 } from "@inkjs/ui";
1048
1175
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1049
- function ConfirmPanel({ commits: commits2, selectedHashes, hasMerge, useMainline, onToggleMainline, onConfirm, onCancel }) {
1176
+ var BASE_BRANCHES = ["main", "master"];
1177
+ function BranchCheck({ targetBranch, onContinue, onBack }) {
1178
+ const [currentBranch, setCurrentBranch] = useState6(null);
1179
+ const [creating, setCreating] = useState6(false);
1180
+ const [error2, setError] = useState6(null);
1181
+ const [matched, setMatched] = useState6(false);
1182
+ const autoCreated = useRef3(false);
1183
+ useEffect4(() => {
1184
+ getCurrentBranch().then((branch2) => {
1185
+ setCurrentBranch(branch2);
1186
+ if (branch2 === targetBranch) {
1187
+ setMatched(true);
1188
+ }
1189
+ });
1190
+ }, [targetBranch]);
1191
+ useEffect4(() => {
1192
+ if (matched) onContinue();
1193
+ }, [matched]);
1194
+ useEffect4(() => {
1195
+ if (currentBranch === null || matched || autoCreated.current) return;
1196
+ if (!BASE_BRANCHES.includes(currentBranch)) return;
1197
+ autoCreated.current = true;
1198
+ setCreating(true);
1199
+ createBranchFrom(targetBranch, currentBranch).then(() => {
1200
+ onContinue();
1201
+ }).catch((err) => {
1202
+ setCreating(false);
1203
+ setError(err.message);
1204
+ });
1205
+ }, [currentBranch, matched, targetBranch]);
1206
+ const doCreate = () => {
1207
+ if (!currentBranch) return;
1208
+ setCreating(true);
1209
+ setError(null);
1210
+ createBranchFrom(targetBranch, currentBranch).then(() => {
1211
+ onContinue();
1212
+ }).catch((err) => {
1213
+ setCreating(false);
1214
+ setError(err.message);
1215
+ });
1216
+ };
1050
1217
  useInput6((input, key) => {
1218
+ if (creating || currentBranch === null || matched) return;
1219
+ if (BASE_BRANCHES.includes(currentBranch)) return;
1220
+ if (input === "y" || input === "Y") {
1221
+ doCreate();
1222
+ } else if (input === "n" || input === "N") {
1223
+ onContinue();
1224
+ } else if (key.escape) {
1225
+ onBack?.();
1226
+ }
1227
+ });
1228
+ if (currentBranch === null || matched) {
1229
+ return /* @__PURE__ */ jsx7(Spinner5, { label: "\u68C0\u67E5\u5F53\u524D\u5206\u652F..." });
1230
+ }
1231
+ if (creating) {
1232
+ return /* @__PURE__ */ jsx7(Spinner5, { label: `\u6B63\u5728\u4ECE ${currentBranch} \u521B\u5EFA\u5206\u652F ${targetBranch}...` });
1233
+ }
1234
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", gap: 1, children: [
1235
+ /* @__PURE__ */ jsx7(SectionHeader, { title: "\u5206\u652F\u68C0\u67E5" }),
1236
+ /* @__PURE__ */ jsxs7(StatusPanel, { type: "warn", title: "\u5F53\u524D\u5206\u652F\u4E0E\u76EE\u6807\u5206\u652F\u4E0D\u4E00\u81F4", children: [
1237
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1238
+ /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " \u5F53\u524D\u5206\u652F: " }),
1239
+ /* @__PURE__ */ jsx7(Text7, { color: "yellow", bold: true, children: currentBranch })
1240
+ ] }),
1241
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1242
+ /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " \u76EE\u6807\u5206\u652F: " }),
1243
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: true, children: targetBranch })
1244
+ ] })
1245
+ ] }),
1246
+ error2 && /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
1247
+ "\u2716 ",
1248
+ error2
1249
+ ] }),
1250
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1251
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, children: [
1252
+ "\u4ECE ",
1253
+ currentBranch,
1254
+ " \u521B\u5EFA ",
1255
+ targetBranch,
1256
+ " \u5206\u652F? "
1257
+ ] }),
1258
+ /* @__PURE__ */ jsx7(InlineKeys, { hints: [
1259
+ { key: "y", label: "\u521B\u5EFA\u5E76\u5207\u6362" },
1260
+ { key: "n", label: "\u8DF3\u8FC7" },
1261
+ { key: "Esc", label: "\u8FD4\u56DE" }
1262
+ ] })
1263
+ ] })
1264
+ ] });
1265
+ }
1266
+
1267
+ // src/components/confirm-panel.tsx
1268
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
1269
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1270
+ function ConfirmPanel({ commits: commits2, selectedHashes, hasMerge, useMainline, noCommit, onToggleMainline, onToggleNoCommit, onConfirm, onCancel }) {
1271
+ useInput7((input, key) => {
1051
1272
  if (key.escape) {
1052
1273
  onCancel();
1053
1274
  } else if (input === "y" || input === "Y") {
@@ -1056,43 +1277,54 @@ function ConfirmPanel({ commits: commits2, selectedHashes, hasMerge, useMainline
1056
1277
  onCancel();
1057
1278
  } else if (hasMerge && (input === "m" || input === "M")) {
1058
1279
  onToggleMainline();
1280
+ } else if (input === "c" || input === "C") {
1281
+ onToggleNoCommit();
1059
1282
  }
1060
1283
  });
1061
1284
  const selectedCommits = selectedHashes.map((hash) => commits2.find((c) => c.hash === hash)).filter(Boolean);
1062
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", gap: 1, children: [
1063
- /* @__PURE__ */ jsx7(SectionHeader, { title: "\u786E\u8BA4\u6267\u884C" }),
1064
- /* @__PURE__ */ jsx7(StatusPanel, { type: "info", title: `cherry-pick --no-commit \xB7 ${selectedCommits.length} \u4E2A commit`, children: selectedCommits.map((c) => /* @__PURE__ */ jsxs7(Box7, { children: [
1065
- /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
1285
+ const modeLabel = noCommit ? "--no-commit" : "\u9010\u4E2A\u63D0\u4EA4";
1286
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
1287
+ /* @__PURE__ */ jsx8(SectionHeader, { title: "\u786E\u8BA4\u6267\u884C" }),
1288
+ /* @__PURE__ */ jsx8(StatusPanel, { type: "info", title: `cherry-pick \xB7 ${modeLabel} \xB7 ${selectedCommits.length} \u4E2A commit`, children: selectedCommits.map((c) => /* @__PURE__ */ jsxs8(Box8, { children: [
1289
+ /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
1066
1290
  " ",
1067
1291
  c.shortHash
1068
1292
  ] }),
1069
- /* @__PURE__ */ jsxs7(Text7, { children: [
1293
+ /* @__PURE__ */ jsxs8(Text8, { children: [
1070
1294
  " ",
1071
1295
  c.message
1072
1296
  ] }),
1073
- /* @__PURE__ */ jsxs7(Text7, { color: "gray", dimColor: true, children: [
1297
+ /* @__PURE__ */ jsxs8(Text8, { color: "gray", dimColor: true, children: [
1074
1298
  " ",
1075
1299
  c.author
1076
1300
  ] })
1077
1301
  ] }, c.hash)) }),
1078
- hasMerge && /* @__PURE__ */ jsxs7(StatusPanel, { type: "warn", title: "\u68C0\u6D4B\u5230 Merge Commit", children: [
1079
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " Cherry-pick \u5408\u5E76\u63D0\u4EA4\u9700\u8981\u6307\u5B9A\u7236\u8282\u70B9 (-m 1)" }),
1080
- /* @__PURE__ */ jsxs7(Box7, { children: [
1081
- /* @__PURE__ */ jsx7(Text7, { children: " " }),
1082
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[m]" }),
1083
- /* @__PURE__ */ jsx7(Text7, { children: " \u5207\u6362 -m 1: " }),
1084
- useMainline ? /* @__PURE__ */ jsx7(Text7, { color: "green", bold: true, children: "\u5DF2\u542F\u7528" }) : /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "\u672A\u542F\u7528" })
1302
+ /* @__PURE__ */ jsxs8(Box8, { children: [
1303
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "[c]" }),
1304
+ /* @__PURE__ */ jsx8(Text8, { children: " \u63D0\u4EA4\u6A21\u5F0F: " }),
1305
+ noCommit ? /* @__PURE__ */ jsxs8(Box8, { children: [
1306
+ /* @__PURE__ */ jsx8(Text8, { color: "yellow", bold: true, children: "--no-commit" }),
1307
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: " (\u6539\u52A8\u6682\u5B58\u5230\u5DE5\u4F5C\u533A\uFF0C\u9700\u624B\u52A8 commit)" })
1308
+ ] }) : /* @__PURE__ */ jsxs8(Box8, { children: [
1309
+ /* @__PURE__ */ jsx8(Text8, { color: "green", bold: true, children: "\u9010\u4E2A\u63D0\u4EA4" }),
1310
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: " (\u4FDD\u7559\u539F\u59CB commit \u4FE1\u606F)" })
1085
1311
  ] })
1086
1312
  ] }),
1087
- /* @__PURE__ */ jsxs7(Box7, { children: [
1088
- /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "\u25B2 " }),
1089
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "--no-commit \u6A21\u5F0F\uFF0C\u6539\u52A8\u5C06\u6682\u5B58\u5230\u5DE5\u4F5C\u533A\uFF0C\u9700\u624B\u52A8 commit" })
1313
+ hasMerge && /* @__PURE__ */ jsxs8(StatusPanel, { type: "warn", title: "\u68C0\u6D4B\u5230 Merge Commit", children: [
1314
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " Cherry-pick \u5408\u5E76\u63D0\u4EA4\u9700\u8981\u6307\u5B9A\u7236\u8282\u70B9 (-m 1)" }),
1315
+ /* @__PURE__ */ jsxs8(Box8, { children: [
1316
+ /* @__PURE__ */ jsx8(Text8, { children: " " }),
1317
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "[m]" }),
1318
+ /* @__PURE__ */ jsx8(Text8, { children: " \u5207\u6362 -m 1: " }),
1319
+ useMainline ? /* @__PURE__ */ jsx8(Text8, { color: "green", bold: true, children: "\u5DF2\u542F\u7528" }) : /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u672A\u542F\u7528" })
1320
+ ] })
1090
1321
  ] }),
1091
- /* @__PURE__ */ jsxs7(Box7, { children: [
1092
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: "\u786E\u8BA4\u6267\u884C? " }),
1093
- /* @__PURE__ */ jsx7(InlineKeys, { hints: [
1322
+ /* @__PURE__ */ jsxs8(Box8, { children: [
1323
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "\u786E\u8BA4\u6267\u884C? " }),
1324
+ /* @__PURE__ */ jsx8(InlineKeys, { hints: [
1094
1325
  { key: "y", label: "\u786E\u8BA4" },
1095
1326
  { key: "n", label: "\u53D6\u6D88" },
1327
+ { key: "c", label: "\u5207\u6362\u63D0\u4EA4\u6A21\u5F0F" },
1096
1328
  ...hasMerge ? [{ key: "m", label: "\u5207\u6362 -m 1" }] : [],
1097
1329
  { key: "Esc", label: "\u8FD4\u56DE" }
1098
1330
  ] })
@@ -1101,92 +1333,426 @@ function ConfirmPanel({ commits: commits2, selectedHashes, hasMerge, useMainline
1101
1333
  }
1102
1334
 
1103
1335
  // src/components/result-panel.tsx
1104
- import { useState as useState6, useEffect as useEffect4 } from "react";
1105
- import { Box as Box8, Text as Text8 } from "ink";
1106
- import { Spinner as Spinner5 } from "@inkjs/ui";
1107
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1108
- function ResultPanel({ selectedHashes, useMainline, stashed, onStashRestored, onDone }) {
1109
- const [phase, setPhase] = useState6("executing");
1110
- const [result, setResult] = useState6(null);
1111
- const [stagedStat, setStagedStat] = useState6("");
1112
- const [stashRestored, setStashRestored] = useState6(null);
1113
- const tryRestoreStash = async () => {
1336
+ import { useState as useState7, useEffect as useEffect5, useCallback as useCallback2, useRef as useRef4 } from "react";
1337
+ import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
1338
+ import { Spinner as Spinner6 } from "@inkjs/ui";
1339
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1340
+ function ResultPanel({ selectedHashes, useMainline, noCommit, stashed, onStashRestored, onDone }) {
1341
+ const [phase, setPhase] = useState7("executing");
1342
+ const [conflictFiles, setConflictFiles] = useState7([]);
1343
+ const [currentIndex, setCurrentIndex] = useState7(0);
1344
+ const [stagedStat, setStagedStat] = useState7("");
1345
+ const [stashRestored, setStashRestored] = useState7(null);
1346
+ const [errorMsg, setErrorMsg] = useState7("");
1347
+ const [completedCount, setCompletedCount] = useState7(0);
1348
+ const [skippedCount, setSkippedCount] = useState7(0);
1349
+ const remainingRef = useRef4([]);
1350
+ const backupBranchRef = useRef4("");
1351
+ const orderedHashes = useRef4([...selectedHashes].reverse());
1352
+ const tryRestoreStash = useCallback2(async () => {
1114
1353
  if (!stashed) return true;
1115
1354
  setPhase("restoring");
1116
1355
  const ok = await stashPop();
1117
1356
  setStashRestored(ok);
1118
1357
  if (ok) onStashRestored();
1119
1358
  return ok;
1120
- };
1121
- useEffect4(() => {
1122
- async function run() {
1123
- const res = await cherryPick(selectedHashes, useMainline);
1124
- setResult(res);
1125
- if (res.success) {
1126
- const stat = await getStagedStat();
1127
- setStagedStat(stat);
1128
- await tryRestoreStash();
1129
- setPhase("done");
1130
- } else {
1131
- await tryRestoreStash();
1132
- setPhase("error");
1359
+ }, [stashed, onStashRestored]);
1360
+ const finishAll = useCallback2(async () => {
1361
+ if (noCommit) {
1362
+ const stat = await getStagedStat();
1363
+ setStagedStat(stat);
1364
+ }
1365
+ if (backupBranchRef.current) {
1366
+ await deleteBackupBranch(backupBranchRef.current);
1367
+ }
1368
+ await tryRestoreStash();
1369
+ setPhase("done");
1370
+ }, [noCommit, tryRestoreStash]);
1371
+ const executeFrom = useCallback2(async (startIndex) => {
1372
+ const hashes = orderedHashes.current;
1373
+ for (let i = startIndex; i < hashes.length; i++) {
1374
+ setCurrentIndex(i);
1375
+ const res = await cherryPickOne(hashes[i], useMainline, noCommit);
1376
+ if (!res.success) {
1377
+ setConflictFiles(res.conflictFiles || []);
1378
+ remainingRef.current = hashes.slice(i + 1);
1379
+ setPhase("conflict");
1380
+ return;
1133
1381
  }
1382
+ setCompletedCount((c) => c + 1);
1134
1383
  }
1135
- run();
1384
+ await finishAll();
1385
+ }, [useMainline, noCommit, finishAll]);
1386
+ useEffect5(() => {
1387
+ createBackupBranch().then((branch2) => {
1388
+ backupBranchRef.current = branch2;
1389
+ executeFrom(0);
1390
+ });
1136
1391
  }, []);
1392
+ const continueRemaining = useCallback2(async () => {
1393
+ const remaining = remainingRef.current;
1394
+ if (remaining.length === 0) {
1395
+ await finishAll();
1396
+ return;
1397
+ }
1398
+ setPhase("executing");
1399
+ for (let i = 0; i < remaining.length; i++) {
1400
+ setCurrentIndex(orderedHashes.current.length - remaining.length + i);
1401
+ const res = await cherryPickOne(remaining[i], useMainline, noCommit);
1402
+ if (!res.success) {
1403
+ setConflictFiles(res.conflictFiles || []);
1404
+ remainingRef.current = remaining.slice(i + 1);
1405
+ setPhase("conflict");
1406
+ return;
1407
+ }
1408
+ setCompletedCount((c) => c + 1);
1409
+ }
1410
+ await finishAll();
1411
+ }, [useMainline, noCommit, finishAll]);
1412
+ const handleContinue = useCallback2(async () => {
1413
+ const conflicts = await getConflictFiles();
1414
+ if (conflicts.length > 0) {
1415
+ setConflictFiles(conflicts);
1416
+ setErrorMsg("\u4ECD\u6709\u672A\u89E3\u51B3\u7684\u51B2\u7A81\u6587\u4EF6\uFF0C\u8BF7\u5148\u89E3\u51B3\u5E76 git add");
1417
+ return;
1418
+ }
1419
+ setPhase("continuing");
1420
+ setErrorMsg("");
1421
+ const inProgress = await isCherryPickInProgress();
1422
+ if (noCommit) {
1423
+ if (inProgress) await clearCherryPickState();
1424
+ setCompletedCount((c) => c + 1);
1425
+ await continueRemaining();
1426
+ } else if (!inProgress) {
1427
+ setCompletedCount((c) => c + 1);
1428
+ await continueRemaining();
1429
+ } else {
1430
+ const contResult = await continueCherryPick();
1431
+ if (contResult.empty) {
1432
+ setPhase("empty");
1433
+ return;
1434
+ }
1435
+ if (!contResult.success) {
1436
+ setConflictFiles(contResult.conflictFiles || []);
1437
+ setErrorMsg("cherry-pick --continue \u5931\u8D25: " + (contResult.error || "").substring(0, 100));
1438
+ setPhase("conflict");
1439
+ return;
1440
+ }
1441
+ setCompletedCount((c) => c + 1);
1442
+ await continueRemaining();
1443
+ }
1444
+ }, [noCommit, continueRemaining]);
1445
+ const handleSkip = useCallback2(async () => {
1446
+ setPhase("continuing");
1447
+ setErrorMsg("");
1448
+ await skipCherryPick();
1449
+ setSkippedCount((c) => c + 1);
1450
+ await continueRemaining();
1451
+ }, [continueRemaining]);
1452
+ const handleAbort = useCallback2(async () => {
1453
+ setPhase("aborting");
1454
+ await abortCherryPick();
1455
+ if (backupBranchRef.current) {
1456
+ const result = await restoreFromBackup(backupBranchRef.current);
1457
+ if (!result.success) {
1458
+ setErrorMsg(result.error || "");
1459
+ }
1460
+ backupBranchRef.current = "";
1461
+ }
1462
+ await tryRestoreStash();
1463
+ setPhase("aborted");
1464
+ }, [tryRestoreStash]);
1465
+ useInput8((input, key) => {
1466
+ if (phase === "done" || phase === "aborted") {
1467
+ onDone();
1468
+ return;
1469
+ }
1470
+ if (phase === "conflict") {
1471
+ if (input === "c" || input === "C") {
1472
+ handleContinue();
1473
+ } else if (input === "a" || input === "A") {
1474
+ handleAbort();
1475
+ } else if (input === "q" || input === "Q") {
1476
+ onDone();
1477
+ }
1478
+ } else if (phase === "empty") {
1479
+ if (input === "s" || input === "S") {
1480
+ handleSkip();
1481
+ } else if (input === "a" || input === "A") {
1482
+ handleAbort();
1483
+ } else if (input === "q" || input === "Q") {
1484
+ onDone();
1485
+ }
1486
+ }
1487
+ });
1137
1488
  if (phase === "executing") {
1138
- return /* @__PURE__ */ jsx8(Spinner5, { label: `cherry-pick --no-commit (${selectedHashes.length} \u4E2A commit)...` });
1489
+ const mode = noCommit ? "--no-commit" : "";
1490
+ return /* @__PURE__ */ jsx9(Spinner6, { label: `cherry-pick ${mode} (${currentIndex + 1}/${orderedHashes.current.length})...` });
1491
+ }
1492
+ if (phase === "continuing") {
1493
+ return /* @__PURE__ */ jsx9(Spinner6, { label: "\u7EE7\u7EED cherry-pick..." });
1494
+ }
1495
+ if (phase === "aborting") {
1496
+ return /* @__PURE__ */ jsx9(Spinner6, { label: "\u6B63\u5728\u653E\u5F03 cherry-pick..." });
1139
1497
  }
1140
1498
  if (phase === "restoring") {
1141
- return /* @__PURE__ */ jsx8(Spinner5, { label: "\u6062\u590D\u5DE5\u4F5C\u533A (git stash pop)..." });
1499
+ return /* @__PURE__ */ jsx9(Spinner6, { label: "\u6062\u590D\u5DE5\u4F5C\u533A (git stash pop)..." });
1500
+ }
1501
+ if (phase === "empty") {
1502
+ const total2 = orderedHashes.current.length;
1503
+ const currentHash = orderedHashes.current[total2 - remainingRef.current.length - 1];
1504
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", gap: 1, children: [
1505
+ /* @__PURE__ */ jsx9(SectionHeader, { title: "\u7A7A\u63D0\u4EA4" }),
1506
+ /* @__PURE__ */ jsx9(StatusPanel, { type: "warn", title: `commit ${currentHash?.substring(0, 7)} \u89E3\u51B3\u51B2\u7A81\u540E\u65E0\u5B9E\u9645\u53D8\u66F4`, children: /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " \u8BE5 commit \u7684\u6240\u6709\u66F4\u6539\u5728\u51B2\u7A81\u89E3\u51B3\u8FC7\u7A0B\u4E2D\u5DF2\u88AB\u4E22\u5F03" }) }),
1507
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(InlineKeys, { hints: [
1508
+ { key: "s", label: "\u8DF3\u8FC7\u6B64 commit (skip)" },
1509
+ { key: "a", label: "\u653E\u5F03\u5168\u90E8 (abort)" },
1510
+ { key: "q", label: "\u9000\u51FA (\u4FDD\u7559\u5F53\u524D\u72B6\u6001)" }
1511
+ ] }) })
1512
+ ] });
1142
1513
  }
1143
- if (phase === "error" && result) {
1144
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
1145
- /* @__PURE__ */ jsx8(SectionHeader, { title: "Cherry-pick \u9047\u5230\u51B2\u7A81" }),
1146
- result.conflictFiles && result.conflictFiles.length > 0 && /* @__PURE__ */ jsx8(StatusPanel, { type: "error", title: "\u51B2\u7A81\u6587\u4EF6", children: result.conflictFiles.map((f) => /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
1514
+ if (phase === "conflict") {
1515
+ const total2 = orderedHashes.current.length;
1516
+ const doneCount = total2 - remainingRef.current.length - 1;
1517
+ const currentHash = orderedHashes.current[total2 - remainingRef.current.length - 1];
1518
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", gap: 1, children: [
1519
+ /* @__PURE__ */ jsx9(SectionHeader, { title: "Cherry-pick \u9047\u5230\u51B2\u7A81" }),
1520
+ /* @__PURE__ */ jsxs9(Box9, { gap: 2, children: [
1521
+ /* @__PURE__ */ jsxs9(Text9, { color: "gray", dimColor: true, children: [
1522
+ "\u8FDB\u5EA6: ",
1523
+ doneCount,
1524
+ "/",
1525
+ total2
1526
+ ] }),
1527
+ /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
1528
+ "\u51B2\u7A81 commit: ",
1529
+ currentHash?.substring(0, 7)
1530
+ ] }),
1531
+ remainingRef.current.length > 0 && /* @__PURE__ */ jsxs9(Text9, { color: "gray", dimColor: true, children: [
1532
+ "\u5269\u4F59: ",
1533
+ remainingRef.current.length
1534
+ ] })
1535
+ ] }),
1536
+ conflictFiles.length > 0 && /* @__PURE__ */ jsx9(StatusPanel, { type: "error", title: "\u51B2\u7A81\u6587\u4EF6", children: conflictFiles.map((f) => /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
1147
1537
  " ",
1148
1538
  f
1149
1539
  ] }, f)) }),
1150
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1151
- /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u25B8 \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add \u548C git commit" }),
1152
- /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: "\u25B8 \u6216\u6267\u884C git cherry-pick --abort \u653E\u5F03\u64CD\u4F5C" })
1540
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1541
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25B8 \u8BF7\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u4E2D\u624B\u52A8\u89E3\u51B3\u51B2\u7A81" }),
1542
+ /* @__PURE__ */ jsxs9(Text9, { color: "gray", dimColor: true, children: [
1543
+ " 1. \u7F16\u8F91\u51B2\u7A81\u6587\u4EF6\uFF0C\u89E3\u51B3\u51B2\u7A81\u6807\u8BB0 ",
1544
+ "<<<<<<< / ======= / >>>>>>>"
1545
+ ] }),
1546
+ /* @__PURE__ */ jsxs9(Text9, { color: "gray", dimColor: true, children: [
1547
+ " 2. \u6267\u884C git add ",
1548
+ "<file>",
1549
+ " \u6807\u8BB0\u5DF2\u89E3\u51B3"
1550
+ ] }),
1551
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: " 3. \u56DE\u5230\u6B64\u5904\u6309 [c] \u7EE7\u7EED" })
1552
+ ] }),
1553
+ errorMsg && /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
1554
+ "\u2716 ",
1555
+ errorMsg
1153
1556
  ] }),
1154
- stashed && stashRestored === false && /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u25B2 stash \u6062\u590D\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8 git stash pop" }),
1155
- stashed && stashRestored === true && /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u2714 \u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)" })
1557
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(InlineKeys, { hints: [
1558
+ { key: "c", label: "\u7EE7\u7EED (\u51B2\u7A81\u5DF2\u89E3\u51B3)" },
1559
+ { key: "a", label: "\u653E\u5F03 (abort)" },
1560
+ { key: "q", label: "\u9000\u51FA (\u4FDD\u7559\u5F53\u524D\u72B6\u6001)" }
1561
+ ] }) })
1156
1562
  ] });
1157
1563
  }
1158
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
1159
- /* @__PURE__ */ jsx8(SectionHeader, { title: "\u540C\u6B65\u5B8C\u6210" }),
1160
- /* @__PURE__ */ jsx8(StatusPanel, { type: "success", title: "\u6682\u5B58\u533A\u53D8\u66F4 (git diff --cached --stat)", children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: stagedStat || "(\u65E0\u53D8\u66F4)" }) }),
1161
- stashed && (stashRestored ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u2714 \u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)" }) : /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u25B2 stash pop \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8 git stash pop" })),
1162
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1163
- /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u25B2 \u6539\u52A8\u5DF2\u6682\u5B58\u5230\u5DE5\u4F5C\u533A (--no-commit \u6A21\u5F0F)" }),
1164
- /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: " \u5BA1\u67E5\u540E\u624B\u52A8\u6267\u884C:" }),
1165
- /* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
1564
+ if (phase === "aborted") {
1565
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", gap: 1, children: [
1566
+ /* @__PURE__ */ jsx9(SectionHeader, { title: "\u5DF2\u653E\u5F03\u64CD\u4F5C" }),
1567
+ errorMsg ? /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
1568
+ "\u2716 ",
1569
+ errorMsg
1570
+ ] }) }) : /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
1571
+ "\u2714 ",
1572
+ "\u5DF2\u56DE\u9000\u5230 cherry-pick \u524D\u7684\u72B6\u6001"
1573
+ ] }),
1574
+ stashed && stashRestored === false && /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25B2 stash \u6062\u590D\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8 git stash pop" }),
1575
+ stashed && stashRestored === true && /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
1576
+ "\u2714 ",
1577
+ "\u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)"
1578
+ ] }),
1579
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "\u6309\u4EFB\u610F\u952E\u9000\u51FA" })
1580
+ ] });
1581
+ }
1582
+ const total = orderedHashes.current.length;
1583
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", gap: 1, children: [
1584
+ /* @__PURE__ */ jsx9(SectionHeader, { title: "\u540C\u6B65\u5B8C\u6210" }),
1585
+ /* @__PURE__ */ jsxs9(Box9, { gap: 2, children: [
1586
+ /* @__PURE__ */ jsxs9(Text9, { color: "green", bold: true, children: [
1587
+ "\u2714 ",
1588
+ completedCount,
1589
+ " \u4E2A commit \u5DF2\u540C\u6B65"
1590
+ ] }),
1591
+ skippedCount > 0 && /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
1592
+ skippedCount,
1593
+ " \u4E2A\u5DF2\u8DF3\u8FC7"
1594
+ ] })
1595
+ ] }),
1596
+ noCommit && /* @__PURE__ */ jsx9(StatusPanel, { type: "success", title: "\u6682\u5B58\u533A\u53D8\u66F4 (git diff --cached --stat)", children: /* @__PURE__ */ jsx9(Text9, { color: "gray", children: stagedStat || "(\u65E0\u53D8\u66F4)" }) }),
1597
+ stashed && (stashRestored ? /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
1598
+ "\u2714 ",
1599
+ "\u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)"
1600
+ ] }) : /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25B2 stash pop \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8 git stash pop" })),
1601
+ noCommit ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1602
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25B2 \u6539\u52A8\u5DF2\u6682\u5B58\u5230\u5DE5\u4F5C\u533A (--no-commit \u6A21\u5F0F)" }),
1603
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: " \u5BA1\u67E5\u540E\u624B\u52A8\u6267\u884C:" }),
1604
+ /* @__PURE__ */ jsxs9(Text9, { color: "cyan", children: [
1166
1605
  " git diff --cached ",
1167
- /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: "# \u67E5\u770B\u8BE6\u7EC6 diff" })
1606
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "# \u67E5\u770B\u8BE6\u7EC6 diff" })
1168
1607
  ] }),
1169
- /* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
1608
+ /* @__PURE__ */ jsxs9(Text9, { color: "cyan", children: [
1170
1609
  ' git commit -m "sync: ..." ',
1171
- /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: "# \u63D0\u4EA4" })
1610
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "# \u63D0\u4EA4" })
1172
1611
  ] }),
1173
- /* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
1612
+ /* @__PURE__ */ jsxs9(Text9, { color: "cyan", children: [
1174
1613
  " git reset HEAD ",
1175
- /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: "# \u6216\u653E\u5F03" })
1614
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "# \u6216\u653E\u5F03" })
1176
1615
  ] })
1177
- ] })
1616
+ ] }) : /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: " \u5DF2\u4FDD\u7559\u539F\u59CB commit \u4FE1\u606F\uFF0C\u53EF\u901A\u8FC7 git log \u67E5\u770B" }) }),
1617
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "\u6309\u4EFB\u610F\u952E\u9000\u51FA" })
1178
1618
  ] });
1179
1619
  }
1180
1620
 
1621
+ // src/components/update-banner.tsx
1622
+ import { useState as useState8, useEffect as useEffect6 } from "react";
1623
+ import { Box as Box10, Text as Text10 } from "ink";
1624
+
1625
+ // src/utils/update-check.ts
1626
+ import https from "https";
1627
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
1628
+ import { join as join2 } from "path";
1629
+ import { homedir } from "os";
1630
+ var PKG_NAME = "git-sync-tui";
1631
+ var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
1632
+ var REQUEST_TIMEOUT = 3e3;
1633
+ function getCachePath() {
1634
+ const dir = join2(homedir(), ".config", "git-sync-tui");
1635
+ return join2(dir, "update-check.json");
1636
+ }
1637
+ function readCache() {
1638
+ try {
1639
+ const raw = readFileSync2(getCachePath(), "utf-8");
1640
+ return JSON.parse(raw);
1641
+ } catch {
1642
+ return null;
1643
+ }
1644
+ }
1645
+ function writeCache(data) {
1646
+ try {
1647
+ const dir = join2(homedir(), ".config", "git-sync-tui");
1648
+ mkdirSync(dir, { recursive: true });
1649
+ writeFileSync2(getCachePath(), JSON.stringify(data));
1650
+ } catch {
1651
+ }
1652
+ }
1653
+ function fetchLatestVersion() {
1654
+ return new Promise((resolve) => {
1655
+ const req = https.get(
1656
+ `https://registry.npmjs.org/${PKG_NAME}/latest`,
1657
+ { timeout: REQUEST_TIMEOUT, headers: { Accept: "application/json" } },
1658
+ (res) => {
1659
+ if (res.statusCode !== 200) {
1660
+ resolve(null);
1661
+ return;
1662
+ }
1663
+ let data = "";
1664
+ res.on("data", (chunk) => {
1665
+ data += chunk.toString();
1666
+ });
1667
+ res.on("end", () => {
1668
+ try {
1669
+ const json = JSON.parse(data);
1670
+ resolve(json.version || null);
1671
+ } catch {
1672
+ resolve(null);
1673
+ }
1674
+ });
1675
+ }
1676
+ );
1677
+ req.on("error", () => resolve(null));
1678
+ req.on("timeout", () => {
1679
+ req.destroy();
1680
+ resolve(null);
1681
+ });
1682
+ });
1683
+ }
1684
+ function compareVersions(current, latest) {
1685
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
1686
+ const c = parse(current);
1687
+ const l = parse(latest);
1688
+ for (let i = 0; i < 3; i++) {
1689
+ if ((l[i] || 0) > (c[i] || 0)) return true;
1690
+ if ((l[i] || 0) < (c[i] || 0)) return false;
1691
+ }
1692
+ return false;
1693
+ }
1694
+ async function checkForUpdate(currentVersion) {
1695
+ const noUpdate = { hasUpdate: false, current: currentVersion, latest: currentVersion };
1696
+ const cache = readCache();
1697
+ if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL) {
1698
+ return {
1699
+ hasUpdate: compareVersions(currentVersion, cache.latest),
1700
+ current: currentVersion,
1701
+ latest: cache.latest
1702
+ };
1703
+ }
1704
+ const latest = await fetchLatestVersion();
1705
+ if (!latest) return noUpdate;
1706
+ writeCache({ latest, checkedAt: Date.now() });
1707
+ return {
1708
+ hasUpdate: compareVersions(currentVersion, latest),
1709
+ current: currentVersion,
1710
+ latest
1711
+ };
1712
+ }
1713
+
1714
+ // src/components/update-banner.tsx
1715
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1716
+ function UpdateBanner({ currentVersion }) {
1717
+ const [info, setInfo] = useState8(null);
1718
+ useEffect6(() => {
1719
+ let cancelled = false;
1720
+ checkForUpdate(currentVersion).then((result) => {
1721
+ if (!cancelled && result.hasUpdate) {
1722
+ setInfo(result);
1723
+ }
1724
+ });
1725
+ return () => {
1726
+ cancelled = true;
1727
+ };
1728
+ }, [currentVersion]);
1729
+ if (!info) return null;
1730
+ return /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { color: "yellow", children: [
1731
+ "\u{1F4A1} ",
1732
+ "\u65B0\u7248\u672C\u53EF\u7528 ",
1733
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: "green", children: info.latest }),
1734
+ /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
1735
+ " (\u5F53\u524D ",
1736
+ info.current,
1737
+ ")"
1738
+ ] }),
1739
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: " \u2192 npm i -g git-sync-tui" })
1740
+ ] }) });
1741
+ }
1742
+
1181
1743
  // src/app.tsx
1182
1744
  import { execSync } from "child_process";
1183
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1745
+ import { createRequire } from "module";
1746
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1747
+ var require2 = createRequire(import.meta.url);
1748
+ var { version: APP_VERSION } = require2("../package.json");
1184
1749
  var STEP_NUMBER = {
1185
1750
  checking: 0,
1186
1751
  "stash-recovery": 0,
1187
1752
  "stash-prompt": 0,
1188
1753
  remote: 1,
1189
1754
  branch: 2,
1755
+ "branch-check": 2,
1190
1756
  commits: 3,
1191
1757
  confirm: 4,
1192
1758
  result: 5
@@ -1194,22 +1760,23 @@ var STEP_NUMBER = {
1194
1760
  var STEP_DEBOUNCE = 100;
1195
1761
  function App({ initialRemote, initialBranch }) {
1196
1762
  const { exit } = useApp();
1197
- const entryStep = initialRemote && initialBranch ? "commits" : initialRemote ? "branch" : "remote";
1198
- const [step, setStepRaw] = useState7("checking");
1199
- const [inputReady, setInputReady] = useState7(true);
1200
- const [remote2, setRemote] = useState7(initialRemote || "");
1201
- const [branch2, setBranch] = useState7(initialBranch || "");
1202
- const [selectedHashes, setSelectedHashes] = useState7([]);
1203
- const [commits2, setCommits] = useState7([]);
1204
- const [hasMerge, setHasMerge] = useState7(false);
1205
- const [useMainline, setUseMainline] = useState7(false);
1206
- const [stashed, setStashed] = useState7(false);
1207
- const [guardTimestamp, setGuardTimestamp] = useState7();
1208
- const stashedRef = useRef3(false);
1209
- const stashRestoredRef = useRef3(false);
1210
- const mountedRef = useRef3(true);
1211
- const debounceTimer = useRef3(null);
1212
- const setStep = useCallback2((newStep) => {
1763
+ const entryStep = initialRemote && initialBranch ? "branch-check" : initialRemote ? "branch" : "remote";
1764
+ const [step, setStepRaw] = useState9("checking");
1765
+ const [inputReady, setInputReady] = useState9(true);
1766
+ const [remote2, setRemote] = useState9(initialRemote || "");
1767
+ const [branch2, setBranch] = useState9(initialBranch || "");
1768
+ const [selectedHashes, setSelectedHashes] = useState9([]);
1769
+ const [commits2, setCommits] = useState9([]);
1770
+ const [hasMerge, setHasMerge] = useState9(false);
1771
+ const [useMainline, setUseMainline] = useState9(false);
1772
+ const [noCommit, setNoCommit] = useState9(false);
1773
+ const [stashed, setStashed] = useState9(false);
1774
+ const [guardTimestamp, setGuardTimestamp] = useState9();
1775
+ const stashedRef = useRef5(false);
1776
+ const stashRestoredRef = useRef5(false);
1777
+ const mountedRef = useRef5(true);
1778
+ const debounceTimer = useRef5(null);
1779
+ const setStep = useCallback3((newStep) => {
1213
1780
  setInputReady(false);
1214
1781
  setStepRaw(newStep);
1215
1782
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
@@ -1217,7 +1784,7 @@ function App({ initialRemote, initialBranch }) {
1217
1784
  if (mountedRef.current) setInputReady(true);
1218
1785
  }, STEP_DEBOUNCE);
1219
1786
  }, []);
1220
- const restoreStashSync = useCallback2(() => {
1787
+ const restoreStashSync = useCallback3(() => {
1221
1788
  if (stashedRef.current && !stashRestoredRef.current) {
1222
1789
  try {
1223
1790
  execSync("git stash pop", { stdio: "ignore" });
@@ -1231,11 +1798,11 @@ function App({ initialRemote, initialBranch }) {
1231
1798
  }
1232
1799
  }
1233
1800
  }, []);
1234
- const markStashRestored = useCallback2(() => {
1801
+ const markStashRestored = useCallback3(() => {
1235
1802
  stashRestoredRef.current = true;
1236
1803
  removeStashGuard();
1237
1804
  }, []);
1238
- useEffect5(() => {
1805
+ useEffect7(() => {
1239
1806
  mountedRef.current = true;
1240
1807
  async function check() {
1241
1808
  const guard = await checkStashGuard();
@@ -1290,9 +1857,10 @@ function App({ initialRemote, initialBranch }) {
1290
1857
  const clean = await isWorkingDirClean();
1291
1858
  if (mountedRef.current) setStep(clean ? entryStep : "stash-prompt");
1292
1859
  };
1293
- const goBack = useCallback2((fromStep) => {
1860
+ const goBack = useCallback3((fromStep) => {
1294
1861
  const backMap = {
1295
1862
  branch: "remote",
1863
+ "branch-check": "branch",
1296
1864
  commits: "branch",
1297
1865
  confirm: "commits"
1298
1866
  };
@@ -1304,10 +1872,10 @@ function App({ initialRemote, initialBranch }) {
1304
1872
  exit();
1305
1873
  }
1306
1874
  }, [setStep, restoreStashSync, exit]);
1307
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1308
- /* @__PURE__ */ jsx9(AppHeader, { step: STEP_NUMBER[step], stashed }),
1309
- step === "checking" && /* @__PURE__ */ jsx9(Spinner6, { label: "\u68C0\u67E5\u5DE5\u4F5C\u533A\u72B6\u6001..." }),
1310
- step === "stash-recovery" && inputReady && /* @__PURE__ */ jsx9(
1875
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1876
+ /* @__PURE__ */ jsx11(AppHeader, { step: STEP_NUMBER[step], stashed, noCommit }),
1877
+ step === "checking" && /* @__PURE__ */ jsx11(Spinner7, { label: "\u68C0\u67E5\u5DE5\u4F5C\u533A\u72B6\u6001..." }),
1878
+ step === "stash-recovery" && inputReady && /* @__PURE__ */ jsx11(
1311
1879
  StashRecovery,
1312
1880
  {
1313
1881
  timestamp: guardTimestamp,
@@ -1315,14 +1883,14 @@ function App({ initialRemote, initialBranch }) {
1315
1883
  onSkip: skipStashRecover
1316
1884
  }
1317
1885
  ),
1318
- step === "stash-prompt" && inputReady && /* @__PURE__ */ jsx9(
1886
+ step === "stash-prompt" && inputReady && /* @__PURE__ */ jsx11(
1319
1887
  StashPrompt,
1320
1888
  {
1321
1889
  onConfirm: doStash,
1322
1890
  onSkip: () => setStep(entryStep)
1323
1891
  }
1324
1892
  ),
1325
- step === "remote" && inputReady && /* @__PURE__ */ jsx9(
1893
+ step === "remote" && inputReady && /* @__PURE__ */ jsx11(
1326
1894
  RemoteSelect,
1327
1895
  {
1328
1896
  onSelect: (r) => {
@@ -1332,49 +1900,58 @@ function App({ initialRemote, initialBranch }) {
1332
1900
  onBack: () => goBack("remote")
1333
1901
  }
1334
1902
  ),
1335
- step === "branch" && inputReady && /* @__PURE__ */ jsx9(
1903
+ step === "branch" && inputReady && /* @__PURE__ */ jsx11(
1336
1904
  BranchSelect,
1337
1905
  {
1338
1906
  remote: remote2,
1339
1907
  onSelect: (b) => {
1340
1908
  setBranch(b);
1341
- setStep("commits");
1909
+ setStep("branch-check");
1342
1910
  },
1343
1911
  onBack: () => goBack("branch")
1344
1912
  }
1345
1913
  ),
1346
- step === "commits" && inputReady && /* @__PURE__ */ jsx9(
1914
+ step === "branch-check" && inputReady && /* @__PURE__ */ jsx11(
1915
+ BranchCheck,
1916
+ {
1917
+ targetBranch: branch2,
1918
+ onContinue: () => setStep("commits"),
1919
+ onBack: () => goBack("branch-check")
1920
+ }
1921
+ ),
1922
+ step === "commits" && inputReady && /* @__PURE__ */ jsx11(
1347
1923
  CommitList,
1348
1924
  {
1349
1925
  remote: remote2,
1350
1926
  branch: branch2,
1351
- onSelect: async (hashes, loadedCommits) => {
1927
+ onSelect: (hashes, loadedCommits) => {
1352
1928
  setSelectedHashes(hashes);
1353
1929
  setCommits(loadedCommits);
1354
- const merge = await hasMergeCommits(hashes);
1355
- setHasMerge(merge);
1356
1930
  setStep("confirm");
1357
1931
  },
1358
1932
  onBack: () => goBack("commits")
1359
1933
  }
1360
1934
  ),
1361
- step === "confirm" && inputReady && /* @__PURE__ */ jsx9(
1935
+ step === "confirm" && inputReady && /* @__PURE__ */ jsx11(
1362
1936
  ConfirmPanel,
1363
1937
  {
1364
1938
  commits: commits2,
1365
1939
  selectedHashes,
1366
1940
  hasMerge,
1367
1941
  useMainline,
1942
+ noCommit,
1368
1943
  onToggleMainline: () => setUseMainline((v) => !v),
1944
+ onToggleNoCommit: () => setNoCommit((v) => !v),
1369
1945
  onConfirm: () => setStep("result"),
1370
1946
  onCancel: () => goBack("confirm")
1371
1947
  }
1372
1948
  ),
1373
- step === "result" && /* @__PURE__ */ jsx9(
1949
+ step === "result" && /* @__PURE__ */ jsx11(
1374
1950
  ResultPanel,
1375
1951
  {
1376
1952
  selectedHashes,
1377
1953
  useMainline,
1954
+ noCommit,
1378
1955
  stashed,
1379
1956
  onStashRestored: markStashRestored,
1380
1957
  onDone: () => {
@@ -1382,12 +1959,16 @@ function App({ initialRemote, initialBranch }) {
1382
1959
  exit();
1383
1960
  }
1384
1961
  }
1385
- )
1962
+ ),
1963
+ /* @__PURE__ */ jsx11(UpdateBanner, { currentVersion: APP_VERSION })
1386
1964
  ] });
1387
1965
  }
1388
1966
 
1389
1967
  // src/cli-runner.ts
1390
1968
  import { createInterface } from "readline";
1969
+ import { createRequire as createRequire2 } from "module";
1970
+ var require3 = createRequire2(import.meta.url);
1971
+ var { version: APP_VERSION2 } = require3("../package.json");
1391
1972
  function log(msg) {
1392
1973
  process.stdout.write(msg + "\n");
1393
1974
  }
@@ -1542,16 +2123,24 @@ ${stat}`);
1542
2123
  process.exit(1);
1543
2124
  }
1544
2125
  }
2126
+ async function printUpdateNotice() {
2127
+ const info = await checkForUpdate(APP_VERSION2);
2128
+ if (info.hasUpdate) {
2129
+ log(`
2130
+ \u{1F4A1} \u65B0\u7248\u672C\u53EF\u7528 ${info.latest} (\u5F53\u524D ${info.current}) \u2192 npm i -g git-sync-tui`);
2131
+ }
2132
+ }
1545
2133
  async function runCli(opts) {
1546
2134
  if (opts.list) {
1547
2135
  await runList(opts);
1548
2136
  } else {
1549
2137
  await runExec(opts);
1550
2138
  }
2139
+ await printUpdateNotice();
1551
2140
  }
1552
2141
 
1553
2142
  // src/cli.tsx
1554
- import { jsx as jsx10 } from "react/jsx-runtime";
2143
+ import { jsx as jsx12 } from "react/jsx-runtime";
1555
2144
  var cli = meow(
1556
2145
  `
1557
2146
  \u7528\u6CD5
@@ -1625,5 +2214,5 @@ if (isCliMode) {
1625
2214
  process.exit(1);
1626
2215
  });
1627
2216
  } else {
1628
- render(/* @__PURE__ */ jsx10(App, { initialRemote: remote, initialBranch: branch }));
2217
+ render(/* @__PURE__ */ jsx12(App, { initialRemote: remote, initialBranch: branch }));
1629
2218
  }