midsummer-sol 0.1.1 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/sol.js +108 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midsummer-sol",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Sol — agent-native version control (a new git). CLI, MCP server, and no-filesystem SDK.",
5
5
  "bin": {
6
6
  "sol": "./sol.js",
package/sol.js CHANGED
@@ -1630,6 +1630,28 @@ async function resolveRef(log, ref) {
1630
1630
  const op = ops.find((o) => o.rootAfter.startsWith(bare) || (o.entryHash ?? "").startsWith(bare)) || die("unknown ref: " + ref);
1631
1631
  return { head: op.rootAfter, op };
1632
1632
  }
1633
+ async function resolveRefSoft(log, ref) {
1634
+ const ops = await log.history();
1635
+ if (!ref || ref === "head" || ref === "HEAD")
1636
+ return { head: await log.head() ?? "", op: ops[ops.length - 1] };
1637
+ if (existsSync6(refsPath())) {
1638
+ const refs = JSON.parse(readFileSync6(refsPath(), "utf8"));
1639
+ if (ref === refs.current) {
1640
+ const h = await log.head() ?? "";
1641
+ return { head: h, op: [...ops].reverse().find((o) => o.rootAfter === h) ?? ops[ops.length - 1] };
1642
+ }
1643
+ const named = refs.branches[ref]?.head ?? refs.tags[ref];
1644
+ if (named !== undefined)
1645
+ return { head: named, op: [...ops].reverse().find((o) => o.rootAfter === named) };
1646
+ }
1647
+ if (/^\d+$/.test(ref)) {
1648
+ const op2 = ops.find((o) => o.seq === Number(ref));
1649
+ return op2 ? { head: op2.rootAfter, op: op2 } : undefined;
1650
+ }
1651
+ const bare = ref.startsWith("h_") ? ref : "h_" + ref;
1652
+ const op = ops.find((o) => o.rootAfter.startsWith(bare) || (o.entryHash ?? "").startsWith(bare));
1653
+ return op ? { head: op.rootAfter, op } : undefined;
1654
+ }
1633
1655
  function materialize(store, head, path) {
1634
1656
  const blob = fileAt(store, head, path);
1635
1657
  if (!blob)
@@ -1656,6 +1678,40 @@ function materialize(store, head, path) {
1656
1678
  }
1657
1679
  return true;
1658
1680
  }
1681
+ function materializeInto(store, head, target, path) {
1682
+ const blob = fileAt(store, head, path);
1683
+ if (!blob)
1684
+ return false;
1685
+ const abs = resolve2(target, path);
1686
+ if (abs !== target && !abs.startsWith(target + sep2))
1687
+ return false;
1688
+ const mode = modeAt(store, head, path);
1689
+ mkdirSync5(dirname3(abs), { recursive: true });
1690
+ if (mode === SYMLINK_MODE2) {
1691
+ try {
1692
+ unlinkSync4(abs);
1693
+ } catch {}
1694
+ symlinkSync3(blob.content, abs);
1695
+ return true;
1696
+ }
1697
+ writeFileSync6(abs, blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
1698
+ if (mode === EXEC_MODE2) {
1699
+ try {
1700
+ chmodSync3(abs, EXEC_MODE2);
1701
+ } catch {}
1702
+ }
1703
+ return true;
1704
+ }
1705
+ function writeWorkingIndexAt(targetSolDir, target, files) {
1706
+ const idx = {};
1707
+ for (const f of files) {
1708
+ try {
1709
+ const st = lstatSync3(join6(target, f));
1710
+ idx[f] = [st.mtimeMs, st.size, st.mode & 73 ? 1 : 0];
1711
+ } catch {}
1712
+ }
1713
+ writeFileSync6(join6(targetSolDir, "index.json"), JSON.stringify(idx));
1714
+ }
1659
1715
  function materializeTree(store, head) {
1660
1716
  const want = new Set(head ? listAll(store, head) : []);
1661
1717
  let n = 0;
@@ -1809,13 +1865,17 @@ function workingChanges(store, head, index = loadWorkingIndex()) {
1809
1865
  }
1810
1866
  return { added: added.sort(), modified: modified.sort(), removed: removed.sort() };
1811
1867
  }
1812
- function printWorkingDiff(store, base) {
1868
+ function printWorkingDiff(store, base, only) {
1813
1869
  const ch = workingChanges(store, base);
1814
- for (const f of ch.added)
1870
+ const keep = (f) => only === undefined || f === only;
1871
+ const added = ch.added.filter(keep);
1872
+ const removed = ch.removed.filter(keep);
1873
+ const modified = ch.modified.filter(keep);
1874
+ for (const f of added)
1815
1875
  console.log(`+ added ${f}`);
1816
- for (const f of ch.removed)
1876
+ for (const f of removed)
1817
1877
  console.log(`- removed ${f}`);
1818
- for (const f of ch.modified) {
1878
+ for (const f of modified) {
1819
1879
  console.log(`~ modified ${f}`);
1820
1880
  const abs = join6(cwd2, f);
1821
1881
  if (lstatSync3(abs).isSymbolicLink())
@@ -1827,8 +1887,16 @@ function printWorkingDiff(store, base) {
1827
1887
  if (typeof stored === "string" && stored !== buf.toString("utf8"))
1828
1888
  console.log(indent(lineHunks2(stored, buf.toString("utf8"))));
1829
1889
  }
1830
- if (!ch.added.length && !ch.removed.length && !ch.modified.length)
1831
- console.log("no working changes");
1890
+ if (!added.length && !removed.length && !modified.length)
1891
+ console.log(only ? `no working changes in ${only}` : "no working changes");
1892
+ }
1893
+ function isWorkingPath(store, head, arg) {
1894
+ const rel = repoRel(arg);
1895
+ if (lexists(join6(cwd2, rel)))
1896
+ return rel;
1897
+ if (head && listAll(store, head).includes(rel))
1898
+ return rel;
1899
+ return;
1832
1900
  }
1833
1901
  function blameFile(ops, store, path) {
1834
1902
  let tagged = [];
@@ -2401,10 +2469,26 @@ async function main() {
2401
2469
  case "diff": {
2402
2470
  const { log } = open();
2403
2471
  const store = loadStore();
2472
+ const head = await log.head() ?? "";
2404
2473
  if (args.length >= 2) {
2405
2474
  printTreeDiff(diffTrees(store, (await resolveRef(log, args[0])).head, (await resolveRef(log, args[1])).head));
2475
+ } else if (args.length === 1) {
2476
+ const resolved = await resolveRefSoft(log, args[0]);
2477
+ if (resolved) {
2478
+ printWorkingDiff(store, resolved.head);
2479
+ } else {
2480
+ const path = isWorkingPath(store, head, args[0]);
2481
+ if (path)
2482
+ printWorkingDiff(store, head, path);
2483
+ else
2484
+ die(`'${args[0]}' is neither a ref nor a working-tree path.
2485
+ usage: sol diff (working tree vs HEAD)
2486
+ sol diff <ref> (working tree vs a branch/commit)
2487
+ sol diff <ref> <ref> (tree vs tree)
2488
+ sol diff <path> (one file, working tree vs HEAD)`);
2489
+ }
2406
2490
  } else {
2407
- printWorkingDiff(store, (await resolveRef(log, args[0])).head);
2491
+ printWorkingDiff(store, head);
2408
2492
  }
2409
2493
  break;
2410
2494
  }
@@ -2872,17 +2956,11 @@ async function main() {
2872
2956
  store.put(node);
2873
2957
  let n = 0;
2874
2958
  if (checkoutHead) {
2875
- for (const f of listAll3(store, checkoutHead)) {
2876
- const abs = resolve3(target, f);
2877
- if (abs !== target && !abs.startsWith(target + sep3))
2878
- continue;
2879
- const blob = fileAt3(store, checkoutHead, f);
2880
- if (!blob)
2881
- continue;
2882
- mkdirSync7(dirname5(abs), { recursive: true });
2883
- writeFileSync9(abs, blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
2884
- n++;
2885
- }
2959
+ const files = listAll3(store, checkoutHead);
2960
+ for (const f of files)
2961
+ if (materializeInto(store, checkoutHead, target, f))
2962
+ n++;
2963
+ writeWorkingIndexAt(fdir, target, files);
2886
2964
  }
2887
2965
  console.log(`cloned ${repoName} -> ${rest[1] || repoName} (${bundle.ops.length} ops, ${Object.keys(cloneBranches).length} branch(es), ${n} files, on ${onBranch})`);
2888
2966
  break;
@@ -2898,8 +2976,9 @@ async function main() {
2898
2976
  const localRefs = existsSync9(refsPath()) ? JSON.parse(readFileSync9(refsPath(), "utf8")) : undefined;
2899
2977
  const branch = localRefs?.current ?? "main";
2900
2978
  const branchHead = await log.head() ?? "";
2979
+ const remoteWasEmpty = rh.seq === 0 && !rh.tip;
2901
2980
  if (localSeq === rh.seq && localTip === rh.tip) {
2902
- console.log("everything up to date");
2981
+ console.log(remoteWasEmpty ? "nothing to push \u2014 local repo is empty (commit something first)" : "everything up to date");
2903
2982
  break;
2904
2983
  }
2905
2984
  if (rh.seq > 0) {
@@ -2936,7 +3015,11 @@ async function main() {
2936
3015
  }
2937
3016
  setOpLogHead(res.head ?? branchHead);
2938
3017
  const prod = res.refs?.production;
2939
- console.log(`pushed ${res.applied} op(s) -> remote (branch ${branch} @ ${(res.head ?? "").slice(0, 12)})${prod ? `; production=${prod}` : ""}`);
3018
+ if (remoteWasEmpty) {
3019
+ console.log(`created remote repo ${cfg.repo} \u2014 pushed ${res.applied} op(s) (branch ${branch} @ ${(res.head ?? "").slice(0, 12)})${prod ? `; production=${prod}` : ""}`);
3020
+ } else {
3021
+ console.log(`pushed ${res.applied} op(s) -> remote (branch ${branch} @ ${(res.head ?? "").slice(0, 12)})${prod ? `; production=${prod}` : ""}`);
3022
+ }
2940
3023
  break;
2941
3024
  }
2942
3025
  case "pull": {
@@ -3108,17 +3191,11 @@ async function main() {
3108
3191
  store.put(node);
3109
3192
  let n = 0;
3110
3193
  if (checkoutHead) {
3111
- for (const f of listAll3(store, checkoutHead)) {
3112
- const abs = resolve3(target, f);
3113
- if (abs !== target && !abs.startsWith(target + sep3))
3114
- continue;
3115
- const blob = fileAt3(store, checkoutHead, f);
3116
- if (!blob)
3117
- continue;
3118
- mkdirSync7(dirname5(abs), { recursive: true });
3119
- writeFileSync9(abs, blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
3120
- n++;
3121
- }
3194
+ const files = listAll3(store, checkoutHead);
3195
+ for (const f of files)
3196
+ if (materializeInto(store, checkoutHead, target, f))
3197
+ n++;
3198
+ writeWorkingIndexAt(fdir, target, files);
3122
3199
  }
3123
3200
  console.log(`forked ${parent} -> ${newRepo} (your copy at ${args[3] || newRepo}: ${Object.keys(cloneBranches).length} branch(es), ${n} files; parent: ${parent})`);
3124
3201
  break;