git-sync-tui 0.1.3 → 0.1.4
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 +318 -90
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -156,38 +156,53 @@ async function addRemote(name, url) {
|
|
|
156
156
|
const git = getGit();
|
|
157
157
|
await git.addRemote(name, url);
|
|
158
158
|
}
|
|
159
|
-
async function getRemoteBranches(
|
|
159
|
+
async function getRemoteBranches(remote2) {
|
|
160
160
|
const git = getGit();
|
|
161
|
+
let fetchOk = false;
|
|
161
162
|
try {
|
|
162
|
-
await git.fetch(
|
|
163
|
+
await git.fetch(remote2);
|
|
164
|
+
fetchOk = true;
|
|
163
165
|
} catch {
|
|
164
166
|
}
|
|
165
167
|
const result = await git.branch(["-r"]);
|
|
166
|
-
const prefix = `${
|
|
167
|
-
|
|
168
|
+
const prefix = `${remote2}/`;
|
|
169
|
+
const branches = result.all.filter((b) => b.startsWith(prefix) && !b.includes("HEAD")).map((b) => b.replace(prefix, "")).sort();
|
|
170
|
+
if (branches.length > 0) return branches;
|
|
171
|
+
try {
|
|
172
|
+
const lsResult = await git.raw(["ls-remote", "--heads", remote2]);
|
|
173
|
+
if (!lsResult.trim()) return [];
|
|
174
|
+
return lsResult.trim().split("\n").map((line) => line.replace(/^.*refs\/heads\//, "")).filter(Boolean).sort();
|
|
175
|
+
} catch {
|
|
176
|
+
if (!fetchOk) {
|
|
177
|
+
throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u4ED3\u5E93 '${remote2}'\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u4ED3\u5E93\u5730\u5740`);
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
168
181
|
}
|
|
169
|
-
async function getCommits(
|
|
182
|
+
async function getCommits(remote2, branch2, count2 = 30) {
|
|
170
183
|
const git = getGit();
|
|
171
|
-
const ref = `${
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
message: "%s",
|
|
180
|
-
author: "%an",
|
|
181
|
-
date: "%ar"
|
|
184
|
+
const ref = `${remote2}/${branch2}`;
|
|
185
|
+
try {
|
|
186
|
+
await git.raw(["rev-parse", "--verify", ref]);
|
|
187
|
+
} catch {
|
|
188
|
+
try {
|
|
189
|
+
await git.fetch(remote2, branch2);
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error(`\u65E0\u6CD5\u83B7\u53D6 ${ref}\uFF0C\u8BF7\u68C0\u67E5\u8FDC\u7A0B\u4ED3\u5E93\u8FDE\u63A5`);
|
|
182
192
|
}
|
|
193
|
+
}
|
|
194
|
+
const result = await git.raw([
|
|
195
|
+
"log",
|
|
196
|
+
ref,
|
|
197
|
+
`--max-count=${count2}`,
|
|
198
|
+
"--format=%H%n%h%n%s%n%an%n%ar%n---"
|
|
199
|
+
]);
|
|
200
|
+
if (!result.trim()) return [];
|
|
201
|
+
const entries = result.trim().split("\n---\n").filter(Boolean);
|
|
202
|
+
return entries.map((block) => {
|
|
203
|
+
const [hash, shortHash, message, author, date] = block.split("\n");
|
|
204
|
+
return { hash, shortHash, message: message || "", author: author || "", date: date || "" };
|
|
183
205
|
});
|
|
184
|
-
return log.all.map((entry) => ({
|
|
185
|
-
hash: entry.hash,
|
|
186
|
-
shortHash: entry.hash.substring(0, 7),
|
|
187
|
-
message: entry.message || "",
|
|
188
|
-
author: entry.author || "",
|
|
189
|
-
date: entry.date || ""
|
|
190
|
-
}));
|
|
191
206
|
}
|
|
192
207
|
async function getMultiCommitStat(hashes) {
|
|
193
208
|
if (hashes.length === 0) return "";
|
|
@@ -405,16 +420,16 @@ function useAsync(fn, deps = []) {
|
|
|
405
420
|
function useRemotes() {
|
|
406
421
|
return useAsync(() => getRemotes(), []);
|
|
407
422
|
}
|
|
408
|
-
function useBranches(
|
|
423
|
+
function useBranches(remote2) {
|
|
409
424
|
return useAsync(
|
|
410
|
-
() =>
|
|
411
|
-
[
|
|
425
|
+
() => remote2 ? getRemoteBranches(remote2) : Promise.resolve([]),
|
|
426
|
+
[remote2]
|
|
412
427
|
);
|
|
413
428
|
}
|
|
414
|
-
function useCommits(
|
|
429
|
+
function useCommits(remote2, branch2, count2 = 30) {
|
|
415
430
|
return useAsync(
|
|
416
|
-
() =>
|
|
417
|
-
[
|
|
431
|
+
() => remote2 && branch2 ? getCommits(remote2, branch2, count2) : Promise.resolve([]),
|
|
432
|
+
[remote2, branch2, count2]
|
|
418
433
|
);
|
|
419
434
|
}
|
|
420
435
|
function useCommitStat(hashes) {
|
|
@@ -445,7 +460,7 @@ function extractRemoteName(url) {
|
|
|
445
460
|
return lastSegment;
|
|
446
461
|
}
|
|
447
462
|
function RemoteSelect({ onSelect, onBack }) {
|
|
448
|
-
const { data: remotes, loading, error, reload } = useRemotes();
|
|
463
|
+
const { data: remotes, loading, error: error2, reload } = useRemotes();
|
|
449
464
|
const [phase, setPhase] = useState3("list");
|
|
450
465
|
const [customUrl, setCustomUrl] = useState3("");
|
|
451
466
|
const [addError, setAddError] = useState3(null);
|
|
@@ -463,10 +478,10 @@ function RemoteSelect({ onSelect, onBack }) {
|
|
|
463
478
|
if (loading) {
|
|
464
479
|
return /* @__PURE__ */ jsx4(Spinner2, { label: "\u83B7\u53D6\u8FDC\u7A0B\u4ED3\u5E93..." });
|
|
465
480
|
}
|
|
466
|
-
if (
|
|
481
|
+
if (error2) {
|
|
467
482
|
return /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
468
483
|
"\u2716 \u83B7\u53D6\u8FDC\u7A0B\u4ED3\u5E93\u5931\u8D25: ",
|
|
469
|
-
|
|
484
|
+
error2
|
|
470
485
|
] });
|
|
471
486
|
}
|
|
472
487
|
if (phase === "adding") {
|
|
@@ -573,8 +588,8 @@ import { useState as useState4, useMemo } from "react";
|
|
|
573
588
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
574
589
|
import { Select as Select2, Spinner as Spinner3, TextInput as TextInput2 } from "@inkjs/ui";
|
|
575
590
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
576
|
-
function BranchSelect({ remote, onSelect, onBack }) {
|
|
577
|
-
const { data: branches, loading, error } = useBranches(
|
|
591
|
+
function BranchSelect({ remote: remote2, onSelect, onBack }) {
|
|
592
|
+
const { data: branches, loading, error: error2 } = useBranches(remote2);
|
|
578
593
|
const [filter, setFilter] = useState4("");
|
|
579
594
|
useInput4((_input, key) => {
|
|
580
595
|
if (key.escape) onBack?.();
|
|
@@ -585,19 +600,19 @@ function BranchSelect({ remote, onSelect, onBack }) {
|
|
|
585
600
|
return filtered.map((b) => ({ label: b, value: b }));
|
|
586
601
|
}, [branches, filter]);
|
|
587
602
|
if (loading) {
|
|
588
|
-
return /* @__PURE__ */ jsx5(Spinner3, { label: `\u83B7\u53D6 ${
|
|
603
|
+
return /* @__PURE__ */ jsx5(Spinner3, { label: `\u83B7\u53D6 ${remote2} \u7684\u5206\u652F\u5217\u8868...` });
|
|
589
604
|
}
|
|
590
|
-
if (
|
|
605
|
+
if (error2) {
|
|
591
606
|
return /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
|
|
592
607
|
"\u2716 \u83B7\u53D6\u5206\u652F\u5217\u8868\u5931\u8D25: ",
|
|
593
|
-
|
|
608
|
+
error2
|
|
594
609
|
] });
|
|
595
610
|
}
|
|
596
611
|
if (!branches || branches.length === 0) {
|
|
597
612
|
return /* @__PURE__ */ jsx5(Text5, { color: "red", children: "\u2716 \u672A\u627E\u5230\u8FDC\u7A0B\u5206\u652F" });
|
|
598
613
|
}
|
|
599
614
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
|
|
600
|
-
/* @__PURE__ */ jsx5(SectionHeader, { title: `\u9009\u62E9\u5206\u652F`, subtitle: `${
|
|
615
|
+
/* @__PURE__ */ jsx5(SectionHeader, { title: `\u9009\u62E9\u5206\u652F`, subtitle: `${remote2} \xB7 ${branches.length} \u4E2A\u5206\u652F` }),
|
|
601
616
|
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
602
617
|
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "/ " }),
|
|
603
618
|
/* @__PURE__ */ jsx5(
|
|
@@ -621,8 +636,8 @@ import { useState as useState5, useMemo as useMemo2, useRef } from "react";
|
|
|
621
636
|
import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
|
|
622
637
|
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
623
638
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
624
|
-
function CommitList({ remote, branch, onSelect, onBack }) {
|
|
625
|
-
const { data:
|
|
639
|
+
function CommitList({ remote: remote2, branch: branch2, onSelect, onBack }) {
|
|
640
|
+
const { data: commits2, loading, error: error2 } = useCommits(remote2, branch2, 30);
|
|
626
641
|
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
627
642
|
const [selectedHashes, setSelectedHashes] = useState5(/* @__PURE__ */ new Set());
|
|
628
643
|
const [shiftMode, setShiftMode] = useState5(false);
|
|
@@ -631,8 +646,8 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
631
646
|
const selectedArray = useMemo2(() => Array.from(selectedHashes), [selectedKey]);
|
|
632
647
|
const { stat, loading: statLoading } = useCommitStat(selectedArray);
|
|
633
648
|
const toggleCurrent = () => {
|
|
634
|
-
if (!
|
|
635
|
-
const hash =
|
|
649
|
+
if (!commits2 || commits2.length === 0) return;
|
|
650
|
+
const hash = commits2[selectedIndex].hash;
|
|
636
651
|
setSelectedHashes((prev) => {
|
|
637
652
|
const next = new Set(prev);
|
|
638
653
|
if (next.has(hash)) {
|
|
@@ -646,49 +661,49 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
646
661
|
});
|
|
647
662
|
};
|
|
648
663
|
const selectRange = (anchor, current) => {
|
|
649
|
-
if (!
|
|
664
|
+
if (!commits2) return;
|
|
650
665
|
const start = Math.min(anchor, current);
|
|
651
666
|
const end = Math.max(anchor, current);
|
|
652
667
|
setSelectedHashes((prev) => {
|
|
653
668
|
const next = new Set(prev);
|
|
654
669
|
for (let i = start; i <= end; i++) {
|
|
655
|
-
next.add(
|
|
670
|
+
next.add(commits2[i].hash);
|
|
656
671
|
}
|
|
657
672
|
return next;
|
|
658
673
|
});
|
|
659
674
|
};
|
|
660
675
|
const toggleAll = () => {
|
|
661
|
-
if (!
|
|
676
|
+
if (!commits2 || commits2.length === 0) return;
|
|
662
677
|
setSelectedHashes((prev) => {
|
|
663
|
-
if (prev.size ===
|
|
678
|
+
if (prev.size === commits2.length) {
|
|
664
679
|
anchorIndexRef.current = null;
|
|
665
680
|
return /* @__PURE__ */ new Set();
|
|
666
681
|
}
|
|
667
|
-
return new Set(
|
|
682
|
+
return new Set(commits2.map((c) => c.hash));
|
|
668
683
|
});
|
|
669
684
|
};
|
|
670
685
|
const invertSelection = () => {
|
|
671
|
-
if (!
|
|
686
|
+
if (!commits2 || commits2.length === 0) return;
|
|
672
687
|
setSelectedHashes((prev) => {
|
|
673
688
|
const next = /* @__PURE__ */ new Set();
|
|
674
|
-
for (const c of
|
|
689
|
+
for (const c of commits2) {
|
|
675
690
|
if (!prev.has(c.hash)) next.add(c.hash);
|
|
676
691
|
}
|
|
677
692
|
return next;
|
|
678
693
|
});
|
|
679
694
|
};
|
|
680
695
|
const selectToCurrent = () => {
|
|
681
|
-
if (!
|
|
696
|
+
if (!commits2 || commits2.length === 0) return;
|
|
682
697
|
setSelectedHashes((prev) => {
|
|
683
698
|
const next = new Set(prev);
|
|
684
699
|
for (let i = 0; i <= selectedIndex; i++) {
|
|
685
|
-
next.add(
|
|
700
|
+
next.add(commits2[i].hash);
|
|
686
701
|
}
|
|
687
702
|
return next;
|
|
688
703
|
});
|
|
689
704
|
};
|
|
690
705
|
useInput5((input, key) => {
|
|
691
|
-
if (!
|
|
706
|
+
if (!commits2 || commits2.length === 0) return;
|
|
692
707
|
if (key.shift) {
|
|
693
708
|
if (!shiftMode) {
|
|
694
709
|
setShiftMode(true);
|
|
@@ -701,7 +716,7 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
701
716
|
setSelectedIndex(newIndex);
|
|
702
717
|
selectRange(anchorIndexRef.current, newIndex);
|
|
703
718
|
} else if (key.downArrow) {
|
|
704
|
-
const newIndex = Math.min(
|
|
719
|
+
const newIndex = Math.min(commits2.length - 1, selectedIndex + 1);
|
|
705
720
|
setSelectedIndex(newIndex);
|
|
706
721
|
selectRange(anchorIndexRef.current, newIndex);
|
|
707
722
|
} else if (input === " ") {
|
|
@@ -717,7 +732,7 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
717
732
|
if (key.upArrow) {
|
|
718
733
|
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
719
734
|
} else if (key.downArrow) {
|
|
720
|
-
setSelectedIndex((prev) => Math.min(
|
|
735
|
+
setSelectedIndex((prev) => Math.min(commits2.length - 1, prev + 1));
|
|
721
736
|
} else if (input === " ") {
|
|
722
737
|
toggleCurrent();
|
|
723
738
|
} else if (input === "a" || input === "A") {
|
|
@@ -730,35 +745,35 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
730
745
|
onBack?.();
|
|
731
746
|
} else if (key.return) {
|
|
732
747
|
if (selectedHashes.size > 0) {
|
|
733
|
-
onSelect(Array.from(selectedHashes),
|
|
748
|
+
onSelect(Array.from(selectedHashes), commits2);
|
|
734
749
|
}
|
|
735
750
|
}
|
|
736
751
|
});
|
|
737
752
|
if (loading) {
|
|
738
|
-
return /* @__PURE__ */ jsx6(Spinner4, { label: `\u83B7\u53D6 ${
|
|
753
|
+
return /* @__PURE__ */ jsx6(Spinner4, { label: `\u83B7\u53D6 ${remote2}/${branch2} \u7684 commit \u5217\u8868...` });
|
|
739
754
|
}
|
|
740
|
-
if (
|
|
755
|
+
if (error2) {
|
|
741
756
|
return /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
742
757
|
"\u2716 \u83B7\u53D6 commit \u5217\u8868\u5931\u8D25: ",
|
|
743
|
-
|
|
758
|
+
error2
|
|
744
759
|
] });
|
|
745
760
|
}
|
|
746
|
-
if (!
|
|
761
|
+
if (!commits2 || commits2.length === 0) {
|
|
747
762
|
return /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u25B2 \u8BE5\u5206\u652F\u6CA1\u6709 commit" });
|
|
748
763
|
}
|
|
749
764
|
const visibleCount = 10;
|
|
750
|
-
const startIdx = Math.max(0, Math.min(selectedIndex - Math.floor(visibleCount / 2),
|
|
751
|
-
const visibleCommits =
|
|
765
|
+
const startIdx = Math.max(0, Math.min(selectedIndex - Math.floor(visibleCount / 2), commits2.length - visibleCount));
|
|
766
|
+
const visibleCommits = commits2.slice(startIdx, startIdx + visibleCount);
|
|
752
767
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 1, children: [
|
|
753
768
|
/* @__PURE__ */ jsx6(SectionHeader, { title: "\u9009\u62E9\u8981\u540C\u6B65\u7684 commit" }),
|
|
754
769
|
/* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
755
770
|
/* @__PURE__ */ jsxs6(Text6, { color: "gray", dimColor: true, children: [
|
|
756
|
-
|
|
771
|
+
remote2,
|
|
757
772
|
"/",
|
|
758
|
-
|
|
773
|
+
branch2
|
|
759
774
|
] }),
|
|
760
775
|
/* @__PURE__ */ jsxs6(Text6, { color: "gray", dimColor: true, children: [
|
|
761
|
-
|
|
776
|
+
commits2.length,
|
|
762
777
|
" commits"
|
|
763
778
|
] }),
|
|
764
779
|
/* @__PURE__ */ jsxs6(Text6, { color: selectedHashes.size > 0 ? "cyan" : "gray", bold: selectedHashes.size > 0, children: [
|
|
@@ -797,9 +812,9 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
797
812
|
] })
|
|
798
813
|
] }, c.hash);
|
|
799
814
|
}),
|
|
800
|
-
startIdx + visibleCount <
|
|
815
|
+
startIdx + visibleCount < commits2.length && /* @__PURE__ */ jsxs6(Text6, { color: "gray", dimColor: true, children: [
|
|
801
816
|
" \u2193 ",
|
|
802
|
-
|
|
817
|
+
commits2.length - startIdx - visibleCount,
|
|
803
818
|
" more"
|
|
804
819
|
] })
|
|
805
820
|
] }),
|
|
@@ -820,7 +835,7 @@ function CommitList({ remote, branch, onSelect, onBack }) {
|
|
|
820
835
|
// src/components/confirm-panel.tsx
|
|
821
836
|
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
822
837
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
823
|
-
function ConfirmPanel({ commits, selectedHashes, hasMerge, useMainline, onToggleMainline, onConfirm, onCancel }) {
|
|
838
|
+
function ConfirmPanel({ commits: commits2, selectedHashes, hasMerge, useMainline, onToggleMainline, onConfirm, onCancel }) {
|
|
824
839
|
useInput6((input, key) => {
|
|
825
840
|
if (key.escape) {
|
|
826
841
|
onCancel();
|
|
@@ -832,7 +847,7 @@ function ConfirmPanel({ commits, selectedHashes, hasMerge, useMainline, onToggle
|
|
|
832
847
|
onToggleMainline();
|
|
833
848
|
}
|
|
834
849
|
});
|
|
835
|
-
const selectedCommits = selectedHashes.map((hash) =>
|
|
850
|
+
const selectedCommits = selectedHashes.map((hash) => commits2.find((c) => c.hash === hash)).filter(Boolean);
|
|
836
851
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", gap: 1, children: [
|
|
837
852
|
/* @__PURE__ */ jsx7(SectionHeader, { title: "\u786E\u8BA4\u6267\u884C" }),
|
|
838
853
|
/* @__PURE__ */ jsx7(StatusPanel, { type: "info", title: `cherry-pick --no-commit \xB7 ${selectedCommits.length} \u4E2A commit`, children: selectedCommits.map((c) => /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
@@ -966,14 +981,15 @@ var STEP_NUMBER = {
|
|
|
966
981
|
result: 5
|
|
967
982
|
};
|
|
968
983
|
var STEP_DEBOUNCE = 100;
|
|
969
|
-
function App() {
|
|
984
|
+
function App({ initialRemote, initialBranch }) {
|
|
970
985
|
const { exit } = useApp();
|
|
986
|
+
const entryStep = initialRemote && initialBranch ? "commits" : initialRemote ? "branch" : "remote";
|
|
971
987
|
const [step, setStepRaw] = useState7("checking");
|
|
972
988
|
const [inputReady, setInputReady] = useState7(true);
|
|
973
|
-
const [
|
|
974
|
-
const [
|
|
989
|
+
const [remote2, setRemote] = useState7(initialRemote || "");
|
|
990
|
+
const [branch2, setBranch] = useState7(initialBranch || "");
|
|
975
991
|
const [selectedHashes, setSelectedHashes] = useState7([]);
|
|
976
|
-
const [
|
|
992
|
+
const [commits2, setCommits] = useState7([]);
|
|
977
993
|
const [hasMerge, setHasMerge] = useState7(false);
|
|
978
994
|
const [useMainline, setUseMainline] = useState7(false);
|
|
979
995
|
const [stashed, setStashed] = useState7(false);
|
|
@@ -1020,7 +1036,7 @@ function App() {
|
|
|
1020
1036
|
}
|
|
1021
1037
|
const clean = await isWorkingDirClean();
|
|
1022
1038
|
if (!mountedRef.current) return;
|
|
1023
|
-
setStep(clean ?
|
|
1039
|
+
setStep(clean ? entryStep : "stash-prompt");
|
|
1024
1040
|
}
|
|
1025
1041
|
check();
|
|
1026
1042
|
const onSignal = () => {
|
|
@@ -1045,7 +1061,7 @@ function App() {
|
|
|
1045
1061
|
stashedRef.current = true;
|
|
1046
1062
|
await writeStashGuard();
|
|
1047
1063
|
}
|
|
1048
|
-
if (mountedRef.current) setStep(
|
|
1064
|
+
if (mountedRef.current) setStep(entryStep);
|
|
1049
1065
|
};
|
|
1050
1066
|
const doStashRecover = async () => {
|
|
1051
1067
|
const entry = await findStashEntry();
|
|
@@ -1055,13 +1071,13 @@ function App() {
|
|
|
1055
1071
|
await removeStashGuard();
|
|
1056
1072
|
if (!mountedRef.current) return;
|
|
1057
1073
|
const clean = await isWorkingDirClean();
|
|
1058
|
-
if (mountedRef.current) setStep(clean ?
|
|
1074
|
+
if (mountedRef.current) setStep(clean ? entryStep : "stash-prompt");
|
|
1059
1075
|
};
|
|
1060
1076
|
const skipStashRecover = async () => {
|
|
1061
1077
|
await removeStashGuard();
|
|
1062
1078
|
if (!mountedRef.current) return;
|
|
1063
1079
|
const clean = await isWorkingDirClean();
|
|
1064
|
-
if (mountedRef.current) setStep(clean ?
|
|
1080
|
+
if (mountedRef.current) setStep(clean ? entryStep : "stash-prompt");
|
|
1065
1081
|
};
|
|
1066
1082
|
const goBack = useCallback2((fromStep) => {
|
|
1067
1083
|
const backMap = {
|
|
@@ -1092,7 +1108,7 @@ function App() {
|
|
|
1092
1108
|
StashPrompt,
|
|
1093
1109
|
{
|
|
1094
1110
|
onConfirm: doStash,
|
|
1095
|
-
onSkip: () => setStep(
|
|
1111
|
+
onSkip: () => setStep(entryStep)
|
|
1096
1112
|
}
|
|
1097
1113
|
),
|
|
1098
1114
|
step === "remote" && inputReady && /* @__PURE__ */ jsx9(
|
|
@@ -1108,7 +1124,7 @@ function App() {
|
|
|
1108
1124
|
step === "branch" && inputReady && /* @__PURE__ */ jsx9(
|
|
1109
1125
|
BranchSelect,
|
|
1110
1126
|
{
|
|
1111
|
-
remote,
|
|
1127
|
+
remote: remote2,
|
|
1112
1128
|
onSelect: (b) => {
|
|
1113
1129
|
setBranch(b);
|
|
1114
1130
|
setStep("commits");
|
|
@@ -1119,8 +1135,8 @@ function App() {
|
|
|
1119
1135
|
step === "commits" && inputReady && /* @__PURE__ */ jsx9(
|
|
1120
1136
|
CommitList,
|
|
1121
1137
|
{
|
|
1122
|
-
remote,
|
|
1123
|
-
branch,
|
|
1138
|
+
remote: remote2,
|
|
1139
|
+
branch: branch2,
|
|
1124
1140
|
onSelect: async (hashes, loadedCommits) => {
|
|
1125
1141
|
setSelectedHashes(hashes);
|
|
1126
1142
|
setCommits(loadedCommits);
|
|
@@ -1134,7 +1150,7 @@ function App() {
|
|
|
1134
1150
|
step === "confirm" && inputReady && /* @__PURE__ */ jsx9(
|
|
1135
1151
|
ConfirmPanel,
|
|
1136
1152
|
{
|
|
1137
|
-
commits,
|
|
1153
|
+
commits: commits2,
|
|
1138
1154
|
selectedHashes,
|
|
1139
1155
|
hasMerge,
|
|
1140
1156
|
useMainline,
|
|
@@ -1159,32 +1175,244 @@ function App() {
|
|
|
1159
1175
|
] });
|
|
1160
1176
|
}
|
|
1161
1177
|
|
|
1178
|
+
// src/cli-runner.ts
|
|
1179
|
+
import { createInterface } from "readline";
|
|
1180
|
+
function log(msg) {
|
|
1181
|
+
process.stdout.write(msg + "\n");
|
|
1182
|
+
}
|
|
1183
|
+
function error(msg) {
|
|
1184
|
+
process.stderr.write(msg + "\n");
|
|
1185
|
+
}
|
|
1186
|
+
function padEnd(str, len) {
|
|
1187
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
1188
|
+
}
|
|
1189
|
+
async function confirm(message) {
|
|
1190
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1191
|
+
return new Promise((resolve) => {
|
|
1192
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
1193
|
+
rl.close();
|
|
1194
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
async function handleStash(noStash2) {
|
|
1199
|
+
const clean = await isWorkingDirClean();
|
|
1200
|
+
if (clean) {
|
|
1201
|
+
log("\u2714 \u5DE5\u4F5C\u533A\u5E72\u51C0");
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
if (noStash2) {
|
|
1205
|
+
log("\u25B2 \u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u53D8\u66F4\uFF08--no-stash \u8DF3\u8FC7 stash\uFF09");
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
log("\u25B2 \u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u53D8\u66F4\uFF0C\u81EA\u52A8 stash...");
|
|
1209
|
+
const ok = await stash();
|
|
1210
|
+
if (ok) {
|
|
1211
|
+
await writeStashGuard();
|
|
1212
|
+
log("\u2714 \u5DF2 stash \u5DE5\u4F5C\u533A\u53D8\u66F4");
|
|
1213
|
+
return true;
|
|
1214
|
+
}
|
|
1215
|
+
error("\u2716 stash \u5931\u8D25");
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
async function restoreStash(stashed) {
|
|
1220
|
+
if (!stashed) return;
|
|
1221
|
+
const ok = await stashPop();
|
|
1222
|
+
await removeStashGuard();
|
|
1223
|
+
if (ok) {
|
|
1224
|
+
log("\u2714 \u5DF2\u6062\u590D\u5DE5\u4F5C\u533A\u53D8\u66F4 (stash pop)");
|
|
1225
|
+
} else {
|
|
1226
|
+
error("\u25B2 stash pop \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: git stash pop");
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async function validateRemote(name) {
|
|
1230
|
+
const remotes = await getRemotes();
|
|
1231
|
+
if (!remotes.some((r) => r.name === name)) {
|
|
1232
|
+
const available = remotes.map((r) => r.name).join(", ");
|
|
1233
|
+
error(`\u2716 \u8FDC\u7A0B\u4ED3\u5E93 '${name}' \u4E0D\u5B58\u5728`);
|
|
1234
|
+
if (available) error(` \u53EF\u7528: ${available}`);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
log(`\u2714 \u8FDC\u7A0B\u4ED3\u5E93 '${name}'`);
|
|
1238
|
+
}
|
|
1239
|
+
async function validateBranch(remote2, branch2) {
|
|
1240
|
+
const branches = await getRemoteBranches(remote2);
|
|
1241
|
+
if (!branches.includes(branch2)) {
|
|
1242
|
+
error(`\u2716 \u5206\u652F '${branch2}' \u4E0D\u5B58\u5728\u4E8E ${remote2}`);
|
|
1243
|
+
const similar = branches.filter((b) => b.toLowerCase().includes(branch2.toLowerCase())).slice(0, 5);
|
|
1244
|
+
if (similar.length > 0) error(` \u7C7B\u4F3C: ${similar.join(", ")}`);
|
|
1245
|
+
process.exit(1);
|
|
1246
|
+
}
|
|
1247
|
+
log(`\u2714 \u5206\u652F '${remote2}/${branch2}'`);
|
|
1248
|
+
}
|
|
1249
|
+
function formatCommitLine(c) {
|
|
1250
|
+
return ` ${c.shortHash} ${padEnd(c.message.slice(0, 60), 62)} ${padEnd(c.author, 16)} ${c.date}`;
|
|
1251
|
+
}
|
|
1252
|
+
async function runList(opts) {
|
|
1253
|
+
await validateRemote(opts.remote);
|
|
1254
|
+
log(`\u83B7\u53D6 ${opts.remote}/${opts.branch} \u7684 commit \u5217\u8868...`);
|
|
1255
|
+
await validateBranch(opts.remote, opts.branch);
|
|
1256
|
+
const commits2 = await getCommits(opts.remote, opts.branch, opts.count);
|
|
1257
|
+
if (commits2.length === 0) {
|
|
1258
|
+
log("(\u65E0 commit)");
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
log(`
|
|
1262
|
+
Commits on ${opts.remote}/${opts.branch} (${commits2.length}):`);
|
|
1263
|
+
for (const c of commits2) {
|
|
1264
|
+
log(formatCommitLine(c));
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
async function runExec(opts) {
|
|
1268
|
+
const stashed = await handleStash(opts.noStash);
|
|
1269
|
+
await validateRemote(opts.remote);
|
|
1270
|
+
await validateBranch(opts.remote, opts.branch);
|
|
1271
|
+
const allCommits = await getCommits(opts.remote, opts.branch, opts.count);
|
|
1272
|
+
const hashes = opts.commits;
|
|
1273
|
+
const resolved = [];
|
|
1274
|
+
for (const input of hashes) {
|
|
1275
|
+
const match = allCommits.find(
|
|
1276
|
+
(c) => c.hash === input || c.shortHash === input || c.hash.startsWith(input)
|
|
1277
|
+
);
|
|
1278
|
+
if (!match) {
|
|
1279
|
+
error(`\u2716 commit '${input}' \u672A\u627E\u5230\u5728 ${opts.remote}/${opts.branch}`);
|
|
1280
|
+
await restoreStash(stashed);
|
|
1281
|
+
process.exit(1);
|
|
1282
|
+
}
|
|
1283
|
+
resolved.push(match);
|
|
1284
|
+
}
|
|
1285
|
+
const resolvedHashes = resolved.map((c) => c.hash);
|
|
1286
|
+
const hasMerge = await hasMergeCommits(resolvedHashes);
|
|
1287
|
+
if (hasMerge && !opts.mainline) {
|
|
1288
|
+
log("\u25B2 \u68C0\u6D4B\u5230 merge commit\uFF0C\u5EFA\u8BAE\u6DFB\u52A0 --mainline (-m) \u53C2\u6570");
|
|
1289
|
+
}
|
|
1290
|
+
log(`
|
|
1291
|
+
Cherry-pick ${resolved.length} \u4E2A commit (--no-commit${opts.mainline ? " -m 1" : ""}):`);
|
|
1292
|
+
for (const c of resolved) {
|
|
1293
|
+
log(formatCommitLine(c));
|
|
1294
|
+
}
|
|
1295
|
+
log("");
|
|
1296
|
+
if (!opts.yes) {
|
|
1297
|
+
const ok = await confirm("\u786E\u8BA4\u6267\u884C?");
|
|
1298
|
+
if (!ok) {
|
|
1299
|
+
log("\u5DF2\u53D6\u6D88");
|
|
1300
|
+
await restoreStash(stashed);
|
|
1301
|
+
process.exit(0);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
const result = await cherryPick(resolvedHashes, opts.mainline);
|
|
1305
|
+
if (result.success) {
|
|
1306
|
+
log("\u2714 Cherry-pick \u5B8C\u6210");
|
|
1307
|
+
const stat = await getStagedStat();
|
|
1308
|
+
if (stat) {
|
|
1309
|
+
log(`
|
|
1310
|
+
\u6682\u5B58\u533A\u53D8\u66F4 (git diff --cached --stat):
|
|
1311
|
+
${stat}`);
|
|
1312
|
+
}
|
|
1313
|
+
log("\n\u25B2 \u6539\u52A8\u5DF2\u6682\u5B58\u5230\u5DE5\u4F5C\u533A (--no-commit \u6A21\u5F0F)");
|
|
1314
|
+
log(" \u5BA1\u67E5\u540E\u624B\u52A8\u6267\u884C:");
|
|
1315
|
+
log(" git diff --cached # \u67E5\u770B\u8BE6\u7EC6 diff");
|
|
1316
|
+
log(' git commit -m "sync: ..." # \u63D0\u4EA4');
|
|
1317
|
+
log(" git reset HEAD # \u6216\u653E\u5F03");
|
|
1318
|
+
} else {
|
|
1319
|
+
error("\u2716 Cherry-pick \u9047\u5230\u51B2\u7A81");
|
|
1320
|
+
if (result.conflictFiles && result.conflictFiles.length > 0) {
|
|
1321
|
+
error("\u51B2\u7A81\u6587\u4EF6:");
|
|
1322
|
+
for (const f of result.conflictFiles) {
|
|
1323
|
+
error(` ${f}`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
error("\n\u25B8 \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add \u548C git commit");
|
|
1327
|
+
error("\u25B8 \u6216\u6267\u884C git cherry-pick --abort \u653E\u5F03\u64CD\u4F5C");
|
|
1328
|
+
}
|
|
1329
|
+
await restoreStash(stashed);
|
|
1330
|
+
if (!result.success) {
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
async function runCli(opts) {
|
|
1335
|
+
if (opts.list) {
|
|
1336
|
+
await runList(opts);
|
|
1337
|
+
} else {
|
|
1338
|
+
await runExec(opts);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1162
1342
|
// src/cli.tsx
|
|
1163
1343
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1164
1344
|
var cli = meow(
|
|
1165
1345
|
`
|
|
1166
1346
|
\u7528\u6CD5
|
|
1167
|
-
$ git-sync-tui
|
|
1347
|
+
$ git-sync-tui [options]
|
|
1168
1348
|
|
|
1169
1349
|
\u9009\u9879
|
|
1170
|
-
--
|
|
1171
|
-
--
|
|
1350
|
+
-r, --remote <name> \u6307\u5B9A\u8FDC\u7A0B\u4ED3\u5E93\u540D\u79F0
|
|
1351
|
+
-b, --branch <name> \u6307\u5B9A\u8FDC\u7A0B\u5206\u652F\u540D\u79F0
|
|
1352
|
+
-c, --commits <hashes> \u6307\u5B9A commit hash\uFF08\u9017\u53F7\u5206\u9694\uFF09
|
|
1353
|
+
-n, --count <number> \u663E\u793A commit \u6570\u91CF\uFF08\u9ED8\u8BA4 30\uFF09
|
|
1354
|
+
-m, --mainline \u5BF9 merge commit \u4F7F\u7528 -m 1
|
|
1355
|
+
-y, --yes \u8DF3\u8FC7\u786E\u8BA4\u76F4\u63A5\u6267\u884C
|
|
1356
|
+
--no-stash \u8DF3\u8FC7 stash \u63D0\u793A
|
|
1357
|
+
--list \u5217\u51FA\u8FDC\u7A0B\u5206\u652F\u7684 commit \u540E\u9000\u51FA
|
|
1172
1358
|
|
|
1173
|
-
\
|
|
1174
|
-
\u4EA4\u4E92\u5F0F TUI \
|
|
1175
|
-
|
|
1359
|
+
\u6A21\u5F0F
|
|
1360
|
+
\u65E0\u53C2\u6570 \u4EA4\u4E92\u5F0F TUI \u6A21\u5F0F
|
|
1361
|
+
-r -b --list \u5217\u51FA commit\uFF08\u7EAF\u6587\u672C\uFF09
|
|
1362
|
+
-r -b -c CLI \u6A21\u5F0F\uFF0C\u786E\u8BA4\u540E\u6267\u884C
|
|
1363
|
+
-r -b -c --yes CLI \u6A21\u5F0F\uFF0C\u76F4\u63A5\u6267\u884C
|
|
1364
|
+
\u4EC5 -r \u6216 -r -b TUI \u6A21\u5F0F\uFF0C\u8DF3\u8FC7\u5DF2\u6307\u5B9A\u6B65\u9AA4
|
|
1176
1365
|
|
|
1177
|
-
\u5FEB\u6377\u952E
|
|
1366
|
+
TUI \u5FEB\u6377\u952E
|
|
1178
1367
|
Space \u9009\u62E9/\u53D6\u6D88 commit
|
|
1179
1368
|
Shift+\u2191/\u2193 \u8FDE\u7EED\u9009\u62E9
|
|
1180
1369
|
a \u5168\u9009/\u53D6\u6D88\u5168\u9009
|
|
1181
1370
|
i \u53CD\u9009
|
|
1182
1371
|
r \u9009\u81F3\u5F00\u5934
|
|
1183
1372
|
Enter \u786E\u8BA4\u9009\u62E9
|
|
1373
|
+
Esc \u8FD4\u56DE\u4E0A\u4E00\u6B65
|
|
1184
1374
|
y/n \u786E\u8BA4/\u53D6\u6D88\u6267\u884C
|
|
1375
|
+
|
|
1376
|
+
\u793A\u4F8B
|
|
1377
|
+
$ git-sync-tui # TUI \u6A21\u5F0F
|
|
1378
|
+
$ git-sync-tui -r upstream -b main --list # \u5217\u51FA commits
|
|
1379
|
+
$ git-sync-tui -r upstream -b main -c abc1234 --yes # \u76F4\u63A5\u6267\u884C
|
|
1380
|
+
$ git-sync-tui -r upstream -b main -c abc1234,def5678 # \u786E\u8BA4\u540E\u6267\u884C
|
|
1381
|
+
$ git-sync-tui -r upstream # TUI \u6A21\u5F0F\uFF0C\u8DF3\u8FC7\u9009\u62E9\u4ED3\u5E93
|
|
1185
1382
|
`,
|
|
1186
1383
|
{
|
|
1187
|
-
importMeta: import.meta
|
|
1384
|
+
importMeta: import.meta,
|
|
1385
|
+
flags: {
|
|
1386
|
+
remote: { type: "string", shortFlag: "r" },
|
|
1387
|
+
branch: { type: "string", shortFlag: "b" },
|
|
1388
|
+
commits: { type: "string", shortFlag: "c" },
|
|
1389
|
+
count: { type: "number", shortFlag: "n", default: 30 },
|
|
1390
|
+
mainline: { type: "boolean", shortFlag: "m", default: false },
|
|
1391
|
+
yes: { type: "boolean", shortFlag: "y", default: false },
|
|
1392
|
+
noStash: { type: "boolean", default: false },
|
|
1393
|
+
list: { type: "boolean", default: false }
|
|
1394
|
+
}
|
|
1188
1395
|
}
|
|
1189
1396
|
);
|
|
1190
|
-
|
|
1397
|
+
var { remote, branch, commits, count, mainline, yes, noStash, list } = cli.flags;
|
|
1398
|
+
var commitList = commits ? commits.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1399
|
+
var hasAllParams = !!(remote && branch && commitList && commitList.length > 0);
|
|
1400
|
+
var isListMode = !!(list && remote && branch);
|
|
1401
|
+
var isCliMode = hasAllParams || isListMode;
|
|
1402
|
+
if (isCliMode) {
|
|
1403
|
+
runCli({
|
|
1404
|
+
remote,
|
|
1405
|
+
branch,
|
|
1406
|
+
commits: commitList,
|
|
1407
|
+
count,
|
|
1408
|
+
mainline,
|
|
1409
|
+
yes,
|
|
1410
|
+
noStash,
|
|
1411
|
+
list
|
|
1412
|
+
}).catch((err) => {
|
|
1413
|
+
console.error("\u2716 " + (err.message || err));
|
|
1414
|
+
process.exit(1);
|
|
1415
|
+
});
|
|
1416
|
+
} else {
|
|
1417
|
+
render(/* @__PURE__ */ jsx10(App, { initialRemote: remote, initialBranch: branch }));
|
|
1418
|
+
}
|
package/package.json
CHANGED