@lisang233/pi-sync 0.2.2 → 0.2.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/README.md +3 -4
- package/package.json +1 -1
- package/src/extension.ts +1 -17
- package/src/git.ts +0 -24
- package/src/operations.ts +15 -37
- package/src/status.ts +1 -1
package/README.md
CHANGED
|
@@ -19,9 +19,9 @@ detection and merging are handled by git itself.
|
|
|
19
19
|
rewritten remote doesn't spuriously conflict.
|
|
20
20
|
- **Git-native three-way merge** — `/sync pull` merges the remote branch into
|
|
21
21
|
the local side. Divergent edits keep local configuration protected while
|
|
22
|
-
holding conflicts in the mirror;
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
holding conflicts in the mirror; `--ours`/`--theirs` or manual edits resolve
|
|
23
|
+
them, and `/sync merge` validates (no markers, valid JSON) before applying to
|
|
24
|
+
local files.
|
|
25
25
|
- **Deliberate commands** — nothing moves your files without being asked;
|
|
26
26
|
`automatic` only observes at session start.
|
|
27
27
|
- **Sensible defaults** — one `pi-sync.json` points at a git remote and branch,
|
|
@@ -48,7 +48,6 @@ pi install npm:@lisang233/pi-sync
|
|
|
48
48
|
/sync status # config + sync state + next step (--diff for content)
|
|
49
49
|
/sync fetch # pull the remote tree without applying
|
|
50
50
|
/sync pull # fetch + merge (--force overwrites local)
|
|
51
|
-
/sync mergetool # launch git mergetool for conflicts ([tool])
|
|
52
51
|
/sync merge # complete merge (--abort, --ours, --theirs)
|
|
53
52
|
/sync push # publish the local tree (--force overwrites remote)
|
|
54
53
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lisang233/pi-sync",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Personal Pi extension that syncs Pi configuration through Git with background auto-sync and git-style fetch/merge conflict handling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/extension.ts
CHANGED
|
@@ -15,17 +15,7 @@ interface BackgroundSync {
|
|
|
15
15
|
settled: Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const COMMANDS = [
|
|
19
|
-
"init",
|
|
20
|
-
"status",
|
|
21
|
-
"push",
|
|
22
|
-
"pull",
|
|
23
|
-
"fetch",
|
|
24
|
-
"merge",
|
|
25
|
-
"mergetool",
|
|
26
|
-
"config",
|
|
27
|
-
"help",
|
|
28
|
-
] as const;
|
|
18
|
+
const COMMANDS = ["init", "status", "push", "pull", "fetch", "merge", "config", "help"] as const;
|
|
29
19
|
|
|
30
20
|
type Subcommand = (typeof COMMANDS)[number];
|
|
31
21
|
|
|
@@ -41,7 +31,6 @@ const USAGE = [
|
|
|
41
31
|
" fetch fetch the remote tree without applying",
|
|
42
32
|
" pull fetch + merge (--force overwrites local)",
|
|
43
33
|
" merge complete merge (--abort, --ours, --theirs)",
|
|
44
|
-
" mergetool launch git mergetool for conflicts ([tool])",
|
|
45
34
|
" push publish local tree (--force overwrites remote)",
|
|
46
35
|
" help show this help",
|
|
47
36
|
].join("\n");
|
|
@@ -226,11 +215,6 @@ async function handleCommand(
|
|
|
226
215
|
theirs: restTokens.some((token) => token === "--theirs"),
|
|
227
216
|
});
|
|
228
217
|
return;
|
|
229
|
-
case "mergetool": {
|
|
230
|
-
const tool = restTokens.find((token) => !token.startsWith("-"));
|
|
231
|
-
await operations.mergetool(ctx, config, tool);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
218
|
case "config":
|
|
235
219
|
await runConfigEditor(ctx.ui, config);
|
|
236
220
|
return;
|
package/src/git.ts
CHANGED
|
@@ -386,30 +386,6 @@ export async function checkoutTheirs(options: GitRunOptions = {}): Promise<void>
|
|
|
386
386
|
await stageAll(options);
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
/**
|
|
390
|
-
* Launch git mergetool interactively in the mirror work tree.
|
|
391
|
-
*/
|
|
392
|
-
export async function launchMergetool(tool?: string, options: GitRunOptions = {}): Promise<void> {
|
|
393
|
-
throwIfAborted(options.signal);
|
|
394
|
-
const repo = gitCwd();
|
|
395
|
-
const args = ["mergetool", "-y"];
|
|
396
|
-
if (tool && tool.trim().length > 0) {
|
|
397
|
-
args.push(`--tool=${tool.trim()}`);
|
|
398
|
-
}
|
|
399
|
-
await new Promise<void>((resolve, reject) => {
|
|
400
|
-
const child = spawn("git", args, {
|
|
401
|
-
cwd: repo,
|
|
402
|
-
stdio: process.stdin.isTTY ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
403
|
-
windowsHide: false,
|
|
404
|
-
});
|
|
405
|
-
child.on("error", reject);
|
|
406
|
-
child.on("close", (code) => {
|
|
407
|
-
if (code === 0) resolve();
|
|
408
|
-
else reject(new Error(`git mergetool exited with status ${code ?? "unknown"}.`));
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
389
|
/** Abort an in-progress merge, restoring the work tree to the pre-merge state. */
|
|
414
390
|
export async function abortMerge(options: GitRunOptions = {}): Promise<void> {
|
|
415
391
|
await runGit(["merge", "--abort"], { cwd: gitCwd(), signal: options.signal });
|
package/src/operations.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
ensureMirror,
|
|
21
21
|
fetchRemote,
|
|
22
22
|
isMergeInProgress,
|
|
23
|
-
launchMergetool,
|
|
24
23
|
listConflictedPaths,
|
|
25
24
|
mergeRemote,
|
|
26
25
|
pushBranch,
|
|
@@ -31,8 +30,10 @@ import {
|
|
|
31
30
|
resetHard,
|
|
32
31
|
stageAll,
|
|
33
32
|
} from "./git.js";
|
|
33
|
+
import { agentDir } from "./paths.js";
|
|
34
34
|
import { classifyState, type StateClassify, syncIndicatorText } from "./status.js";
|
|
35
35
|
import {
|
|
36
|
+
agentTarget,
|
|
36
37
|
collectAgentFiles,
|
|
37
38
|
copyMirrorToAgent,
|
|
38
39
|
graftAgentIntoMirror,
|
|
@@ -124,7 +125,7 @@ function nextStepHint(info: StateClassify): string {
|
|
|
124
125
|
case "behind":
|
|
125
126
|
return "next: /sync pull to fetch and apply remote changes";
|
|
126
127
|
case "conflict":
|
|
127
|
-
return "next: /sync merge (--ours
|
|
128
|
+
return "next: /sync merge (--ours or --theirs), or /sync pull --force to overwrite local";
|
|
128
129
|
case "unknown":
|
|
129
130
|
return "next: /sync fetch to check the remote";
|
|
130
131
|
}
|
|
@@ -227,7 +228,7 @@ export async function pull(
|
|
|
227
228
|
// ignore
|
|
228
229
|
}
|
|
229
230
|
const conflictList = conflicts.length > 0 ? ` (${conflicts.join(", ")})` : "";
|
|
230
|
-
const message = `Merge conflict in ${conflicts.length} file(s)${conflictList}. Local configuration was preserved.
|
|
231
|
+
const message = `Merge conflict in ${conflicts.length} file(s)${conflictList}. Local configuration in ${agentDir()} was preserved. Edit files or resolve with /sync merge --ours / --theirs. Then run /sync merge. (remote ${shortId(
|
|
231
232
|
remoteRevision,
|
|
232
233
|
)})`;
|
|
233
234
|
ctx.ui.notify(message, "warning");
|
|
@@ -403,9 +404,18 @@ export async function merge(
|
|
|
403
404
|
if (errors.length > 0) {
|
|
404
405
|
const errorDetails = errors
|
|
405
406
|
.slice(0, 5)
|
|
406
|
-
.map((e) => ` - ${e.path}${e.line ? `:${e.line}` : ""}: ${e.reason}`)
|
|
407
|
+
.map((e) => ` - ${agentTarget(e.path)}${e.line ? `:${e.line}` : ""}: ${e.reason}`)
|
|
407
408
|
.join("\n");
|
|
408
|
-
const message =
|
|
409
|
+
const message = [
|
|
410
|
+
`Cannot complete merge: ${errors.length} unresolved issue(s) detected.`,
|
|
411
|
+
errorDetails,
|
|
412
|
+
"",
|
|
413
|
+
`To resolve manually, edit files in: ${agentDir()}`,
|
|
414
|
+
"Or choose a resolution strategy:",
|
|
415
|
+
" /sync merge --ours (keep local configuration)",
|
|
416
|
+
" /sync merge --theirs (use remote configuration)",
|
|
417
|
+
" /sync merge --abort (discard merge and restore)",
|
|
418
|
+
].join("\n");
|
|
409
419
|
ctx.ui.notify(message, "error");
|
|
410
420
|
return { pushed: false, pulled: false, merged: false, message };
|
|
411
421
|
}
|
|
@@ -420,38 +430,6 @@ export async function merge(
|
|
|
420
430
|
return { pushed: false, pulled: false, merged: true, message };
|
|
421
431
|
}
|
|
422
432
|
|
|
423
|
-
export async function mergetool(
|
|
424
|
-
ctx: CommandContext,
|
|
425
|
-
config: SyncConfig,
|
|
426
|
-
tool?: string,
|
|
427
|
-
): Promise<SyncResult> {
|
|
428
|
-
if (!(await isMergeInProgress({ signal: ctx.signal }))) {
|
|
429
|
-
const message =
|
|
430
|
-
"No merge in progress. Run /sync pull to merge remote changes into local files.";
|
|
431
|
-
ctx.ui.notify(message, "info");
|
|
432
|
-
return { pushed: false, pulled: false, merged: false, message };
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
try {
|
|
436
|
-
await launchMergetool(tool, { signal: ctx.signal });
|
|
437
|
-
await refreshIndicator(ctx, config);
|
|
438
|
-
const conflicts = await listConflictedPaths({ signal: ctx.signal });
|
|
439
|
-
if (conflicts.length === 0) {
|
|
440
|
-
ctx.ui.notify("Mergetool finished. Run /sync merge to validate and apply.", "info");
|
|
441
|
-
} else {
|
|
442
|
-
ctx.ui.notify(
|
|
443
|
-
`Mergetool finished, but ${conflicts.length} file(s) still conflicted.`,
|
|
444
|
-
"warning",
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
return { pushed: false, pulled: false, merged: false, message: "mergetool" };
|
|
448
|
-
} catch (error) {
|
|
449
|
-
const message = `Failed to run mergetool: ${error instanceof Error ? error.message : String(error)}`;
|
|
450
|
-
ctx.ui.notify(message, "error");
|
|
451
|
-
return { pushed: false, pulled: false, merged: false, message };
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
433
|
function describeChanges(summary: ReturnType<typeof diffSummary>): string {
|
|
456
434
|
const parts: string[] = [];
|
|
457
435
|
if (summary.added > 0) parts.push(`${summary.added} added`);
|
package/src/status.ts
CHANGED
|
@@ -135,6 +135,6 @@ export function syncIndicatorText(info: SyncStatusInfo): string {
|
|
|
135
135
|
case "behind":
|
|
136
136
|
return `sync: ${info.behind} behind — pull`;
|
|
137
137
|
case "conflict":
|
|
138
|
-
return `sync: conflict (${info.conflicts}) — /sync merge
|
|
138
|
+
return `sync: conflict (${info.conflicts}) — /sync merge`;
|
|
139
139
|
}
|
|
140
140
|
}
|